sections
The plural collection concept: given a search query (an SQO) or a set of
locators, resolve the matching records of a section_tipo, enumerate their
section_ids, and perform bulk deletes. It answers "which records of this
section exist?" — not the one that reads or writes a single record.
See also: Sections concept · section · section_record · Components
There is no sections object to instance. The contract — the envelope
shape and the SQO-normalization rules — is a pure spec in
src/core/concepts/sections.ts; the actual search → rows → per-record fan-out
is a set of plain async functions in src/core/section/read.ts (readSection,
readSectionRows, deriveSectionDdoMap). A caller just calls the function with
an Rqo.
Role
Where the section concept doc explains the matrix-table model — a
logical record is the pair (section_tipo, section_id), and all records of
every section live in one matrix table — the sections concept operates over
the collection of records that share a section_tipo. It does not own a
record's data or its database I/O; it runs a search and hands back the result.
Three sibling concepts share the "section" name and are easy to confuse. Keep the distinction crisp:
| concept | scope | owns | typical job | module home |
|---|---|---|---|---|
section (singular) |
one section type (one section_tipo) |
the table abstraction: components, permissions, the shared relations array |
build a section's context, resolve its children | src/core/concepts/section.ts, src/core/section/context.ts |
section_record |
one record row (section_tipo, section_id) |
the physical per-record I/O (read / save / delete / duplicate) | load, persist or delete a single row in matrix |
src/core/concepts/section_record.ts, src/core/section_record/, src/core/section/record/ |
sections (plural — this concept) |
the collection of records of a section_tipo |
nothing persistent; it runs a search over the matrix | list the section_ids, fetch list data, bulk-delete records matching a query |
src/core/concepts/sections.ts, src/core/section/read.ts |
In short: section is the type, section_record is one row, and sections is
"the set of rows". sections never touches a column directly — it delegates
reads to the search / SQO machinery and per-record deletes to the
section_record write path.
The SQO-normalization contract
src/core/concepts/sections.ts is the pure-contract home for two rules:
- SQO cloning + limit defaulting. The caller's SQO is never mutated in
place.
limitdefaults toSECTIONS_EDIT_LIMIT = 1in edit mode, otherwise toSECTIONS_DEFAULT_LIMIT = 10unless the caller section's ownrequest_configsupplies one.selectis forced to[]so the search returns only(section_tipo, section_id)— every value resolves later, per record, through the structure-context/subdatum machinery. The engine applies this inreadSectionRows()(src/core/section/read.ts): see theclientLimitMissinghandling there. - Envelope shape. The
data[]array leads with one envelope item{typo:'sections', tipo, section_tipo:[], entries:[...]}(SECTIONS_ENVELOPE_TYPO = 'sections'). Each entry is a locator carryingpaginated_key = row_index + sqo.offset. An empty result still emits this envelope (context only, no crash).
// src/core/concepts/sections.ts
export const SECTIONS_DEFAULT_LIMIT = 10;
export const SECTIONS_EDIT_LIMIT = 1;
export const SECTIONS_ENVELOPE_TYPO = 'sections';
The engine — src/core/section/read.ts
| function | purpose / returns |
|---|---|
readSection(rqo, principal?) |
The full read: context[] (deduplicated by contextKey, first occurrence wins) plus data[] (the sections envelope followed by one item per resolved component ddo, record-major/ddo-minor). The single entry point for both list and edit reads. |
readSectionRows(rqo, principal?) |
Runs the SQO through the search engine (pickReadSource → the matrix source or the Time Machine tm source), builds the envelope, and drives the per-row/per-ddo emission (emitDdoData). |
deriveSectionDdoMap(sectionTipo, ownerSectionTipo, mode) |
When the client sends no show.ddo_map, derive the section's default columns from its ontology request_config (the v5/v6 builders). |
There is no shared "enumerate every matching section_id, ignoring pagination"
helper. A caller that needs one (limit 0, offset 0, full_count = false,
select = []) builds its own no-limit search over buildSearchSql
(src/core/search/sql_assembler.ts) — tools/tool_update_cache is the worked
example.
Bulk delete — the delete API action
The delete action handler (src/core/api/handlers/dd_core_api.ts) is the
API's single delete entry point, for both one record and an SQO-matched set:
// the 'delete' action (essence)
const sectionTipo = source.section_tipo ?? source.tipo;
const deleteMode = source.delete_mode ?? 'delete_data'; // the default
// ... permission gate, area-write refusal ...
const targets = source.section_id !== undefined
? [Number(source.section_id)]
: /* SQO-matched ids, global-admin only */ matchedIds;
for (const targetId of targets) {
const outcome = deleteMode === 'delete_record'
? await deleteSectionRecord(sectionTipo, targetId, principal.userId)
: await deleteSectionData(sectionTipo, targetId, principal.userId);
}
Its guarantees, in the order they are enforced:
- Permissions. Requires
getSectionPermissions(principal, sectionTipo) >= 2(src/core/api/handlers/dd_core_api.ts); refused otherwise. - Per-record scope gate. A non-global-admin deleting an explicit
section_idmust have that record inside their projects filter (isRecordInScope(),src/core/security/record_scope.ts). Without this, a level-2 user could delete a record they can never see — a cross-project IDOR. Global admins are unscoped. - Multiple-delete guard. An SQO-based (rather than explicit-id) delete
requires
principal.isGlobalAdmin, and fails closed. - Cross-section leak guard. When deleting from an SQO, matched rows are
filtered back down to the gated
sectionTipobefore any delete runs. - Ontology cascade. Deleting a
hierarchy1/ontology35registry record (ONTOLOGY_MAIN_SECTIONS,src/core/ontology/ontology_delete.ts) indelete_recordmode uninstalls its whole TLD — everydd_ontologynode, the main record, and every node record. Global-admin only, matching the blast radius. delete_modechooses the per-record operation:delete_recordcallsdeleteSectionRecord()(full removal, Time-Machine-backed — seesection_record);delete_data(the default) callsdeleteSectionData()(clears the data, keeps the row).
A parent with children is skipped, not orphaned
A delete_record on a tree/thesaurus record that still has
component_relation_children is refused for that record: it is skipped
and its child ids are enumerated back in the response errors, so the subtree
is never orphaned. The check uses the computed inverse children lookup, not
a stored value.
Pass options.delete_with_children to bypass it deliberately. It never
applies in delete_data mode (which empties the row but keeps it, so nothing
can be orphaned), and it does not block the ontology cascade above — that
path legitimately tears down whole trees.
When it is used
Reach for the sections concept (readSection/readSectionRows, or the
delete action) when you are working with the set of records of a
section_tipo, not a single one:
- List mode / search results. The
dd_core_api.readaction, for alistortmmode read, callsreadSection()with the client's navigation SQO; the returned{context, data}envelope is what the client list view (section_list) renders. - Enumerating / iterating every record. When a tool needs every matching
section_id(e.g. to loop and re-save each), build a no-limit search overbuildSearchSqldirectly (seetool_update_cache) rather than paginating. - Bulk delete. The
deleteAPI action is the single chokepoint for permission checks, the scope and multiple-delete guards, the children refusal, and ontology coherence — whether the caller passes onesection_idor an SQO matching many.
Use the singular section instead when you need one section
type's context, permissions, or buttons, and
section_record when you need to read, save or delete
one specific row. The sections collection concept is the wrong tool for
editing a single record: it only locates and bulk-removes.
Examples
Read a list page
import { readSection } from '../section/read.ts';
const result = await readSection(
{
action: 'search',
source: { tipo: 'oh1', section_tipo: 'oh1', mode: 'list', lang: 'lg-spa' },
sqo: { section_tipo: ['oh1'], limit: 10, offset: 0 },
},
principal,
);
// result.data[0] → { typo: 'sections', tipo: 'oh1', section_tipo: [], entries: [...] }
// result.data[1…] → one item per resolved component ddo, per record
Bulk delete a set of records
// via the API 'delete' action — sqo-matched deletes require a global-admin
// principal; a single explicit section_id does not.
const rqo = {
action: 'delete',
source: { section_tipo: 'oh1', delete_mode: 'delete_record' },
sqo: { section_tipo: ['oh1'], filter_by_locators: [{ section_tipo: 'oh1', section_id: 127 }] },
};
// → { result: ['127'], msg: 'OK. Request done' }
Related
- Sections concept — the matrix-table model and the section module family.
- section — the singular section type (table abstraction & orchestrator).
- section_record — physical per-record database I/O.
- Components — the fields that live inside a section.
- SQO — the Search Query Object machinery
sectionsdelegates every query to. - Locator — the pointer type accepted in filters and returned in
entries.