Reapply "feat(hub-projects): detect data tables from low-code app DB-table config"

This reverts commit 112844deea.
This commit is contained in:
tristantr
2026-07-13 16:07:59 +02:00
parent 709e03e658
commit 14abefb4f6
2 changed files with 59 additions and 0 deletions
@@ -92,6 +92,36 @@ describe('detectDatatableTables', () => {
// public-schema ref keeps the bare name; non-public keeps schema.table.
expect([...(usage.get('main') ?? [])].sort()).toEqual(['app1.orders', 'customers'])
})
it('reads the DB-table component config of a low-code app', async () => {
const appValue = {
grid: [
{
data: {
type: 'dbexplorermulticomponent',
configuration: {
type: {
type: 'oneOf',
selected: 'datatable',
configuration: {
postgresql: {
resource: { type: 'static', value: '' },
table: { type: 'static', value: undefined }
},
datatable: {
datatable: { type: 'static', value: 'datatable://main' },
table: { type: 'static', value: 'customers' }
}
}
}
}
}
}
]
}
const usage = await detectDatatableTables([{ kind: 'app', path: 'f/p/app', value: appValue }])
expect([...(usage.get('main') ?? [])]).toEqual(['customers'])
})
})
describe('generateDatatableMigrations', () => {
@@ -59,6 +59,32 @@ function addUsage(map: Map<string, Set<string>>, path: string): void {
addDatatableTable(map, datatable, table)
}
// Low-code apps declare data table usage explicitly on the DB-table component:
// a `oneOf` `type` config with `selected === 'datatable'` holding the data table
// (as `datatable://<name>`) and the table. Walk the app value for those instead
// of parsing assets (low-code inline scripts don't carry a persisted asset list).
function collectAppDatatableConfigs(node: any, map: Map<string, Set<string>>): void {
if (node == null || typeof node !== 'object') return
if (Array.isArray(node)) {
for (const v of node) collectAppDatatableConfigs(v, map)
return
}
if (node.selected === 'datatable' && node.configuration?.datatable) {
const dtRaw = node.configuration.datatable.datatable?.value
const tableRaw = node.configuration.datatable.table?.value
if (typeof dtRaw === 'string' && dtRaw.trim() !== '') {
const datatable = dtRaw
.trim()
.replace(/^\$res:/, '')
.replace(/^datatable:\/\//, '')
const table =
typeof tableRaw === 'string' && tableRaw.trim() !== '' ? tableRaw.trim() : undefined
addDatatableTable(map, datatable, table)
}
}
for (const k of Object.keys(node)) collectAppDatatableConfigs(node[k], map)
}
/**
* Scan a project's fetched items for data table usage and return
* `datatable -> set of table refs` (a table ref is `table` or `schema.table`).
@@ -66,6 +92,7 @@ function addUsage(map: Map<string, Set<string>>, path: string): void {
* - flows: read each module's stored `assets`
* - full-code (raw) apps: read the explicit `data.tables` declaration; fall back
* to `runnables[key].inlineScript.assets` for older apps
* - low-code apps: read the DB-table component's explicit datatable/table config
*/
export async function detectDatatableTables(
items: FetchedItem[]
@@ -116,6 +143,8 @@ export async function detectDatatableTables(
for (const a of assets)
if (a?.kind === 'datatable' && typeof a.path === 'string') addUsage(map, a.path)
}
} else if (item.kind === 'app') {
collectAppDatatableConfigs(item.value, map)
}
}
return map