Repository navigation
Conversation
A view can now exist without a table and carries a type (table or grid), so an application can hold pages that are not tied to one table. - tables_views gets a type column and table_id becomes nullable - a view without a table is owned by its creator and listed for them - row, column, import, relation, permission and activity code skips views without a table instead of failing on the missing table - views without a table are removed with their owner Co-authored-by: Ruben van der Linde <ruben@conduction.nl> Assisted-by: ClaudeCode:claude-opus-5-5 Signed-off-by: Remko <remko@conduction.nl>
Federated views have no table_id either, so a missing table alone does not mean a view is standalone. View::isStandalone() checks for no table and no external id, and the guards, the owner fallback and the query for a user's standalone views now use it. - a row update, delete or presence check against a view without a table no longer ends in a TypeError: the view holds no rows - the view type is validated with the rest of the input and is typed as table or grid in the API - the migration no longer makes table_id nullable, it already is Assisted-by: ClaudeCode:claude-opus-5-5 Signed-off-by: Remko <remko@conduction.nl>
The unique index on (table_id, technical_name) does not hold for a missing table on MySQL, PostgreSQL and SQLite, and on Oracle it makes a name unique across the whole instance. A view without a table is therefore always named view_<id>: create and update refuse a chosen name, and an import ignores the name from its source. The page a user names is the application's menu item, whose technical name is unique within the application. Assisted-by: ClaudeCode:claude-opus-5-5 Signed-off-by: Remko <remko@conduction.nl>
blizzz
left a comment
There was a problem hiding this comment.
And a general question:
- What is the motivation on the change on Technical name? They are not dependent upon a table. Technical names are user-provided aliases for easier access in in external handlers (e.g. external Flows).
Correct a later pr in the 3rd stack wil cover the export per application but not the account migration so we will fix that
We traced it back and we see that this was already a 500 for normal views too but with the gridviews we have made it is now easier to reach. This is now done in 4a25495
Correct. The app always creates a fresh UUID (v7), so duplicates only come from import or manual database work. Tables already has an index on |
- the row checks use isStandalone(), like the rest of the row code - deleting a user's views without a table lives in ViewService; the listener only calls it - a grid view cannot have a table: refused on create and when changing the type - changing or deleting a row through a view without a table is a 404 instead of a 500 - a view's uuid is unique on its own: duplicates get a fresh uuid in a migration before the index is added, and an import only keeps the source uuid when it is still free Assisted-by: ClaudeCode:claude-opus-5-5 Signed-off-by: Remko <remko@conduction.nl>
- a proxy view no longer copies the uuid of the remote view, so one view federated to several users on the same instance does not clash on the uuid index of views; tables keep the source uuid as on main - the migration keeps the uuid on the local view when a proxy shares it, and drops the old (table_id, uuid) index that the uuid-only index covers - the row checks say that federated views read their rows through the federation proxy Assisted-by: ClaudeCode:claude-opus-5-5 Signed-off-by: Remko <remko@conduction.nl>
Nah, we cannot have UUIDs on views alone. The naming is not ideal here. While the value is created from UUID provider, the use case is slightly bigger. We introduced them to be able to update a Table schema, and we had to have a reliable identifier, that would also work across instances. At the same time, you should be able to import the same schema in different tables. Hence the connector between table ID and view UUID. This will break, when view UUIDs turn unique. The difference was tables – looks like a inconsistency here – is that they were not needed at all originally. Only when we applied the same mechanics to contexts. And thus one other question, unless @enjeck asked it elsewhere already, what is the underlying idea to use extend the case of views, instead of adding such a functionality within Contexts/Applications? My understand is to use it as an element on a context page, but it would not strictly need to be a view. Unless all other View functionalities would be used (but where do the rows come from? I assume I'd find the answer in follow-up works?) |

Views can now exist without a table.
With this PR we prepare for the use of pages inside an application.
Views also get a
type(tableorgrid).Every existing view becomes
table, so nothing changes for current users.What changes
tables_viewsgets atypecolumn with the defaulttable.table_idwas already nullable (for federated views), so a view without a table needs no other schema change.ViewService::create()accepts no table when the type is nottable. Atableview still needs a table, and a view without a table cannot be changed totablelater.view_<id>). Setting a technical name on it is rejected, and an import drops it. The unique index on(table_id, technical_name)treats a missing table differently per database.tableIdis now nullable andtypeis added to the view response (OpenAPI and TS types regenerated).typecan already be changed through the existing view update endpoints. There is no new endpoint yet; creating a view without a table through the API comes in a later PR of this stack.🖼️ Screenshots
Backend only, no UI changes.
🏁 Checklist
/backport to stableX.X🤖 AI (if applicable)