Runtime Schema API
OpenRegister exposes runtime CRUD on /api/schemas and /api/registers
so a non-admin citizen developer (driven by an in-Nextcloud builder UI
such as OpenBuild's schema editor) can author a full data model without
shipping a PHP _register.json PR. This document captures the contract
that makes those routes correct for runtime use: cache invalidation,
DELETE safety, auto-Register provisioning on import, and the
slug-aware search helper.
Cache invalidation on mutation
Every successful POST / PUT / PATCH / DELETE on a schema or a
register calls into the cache handler before the controller returns:
SchemaCacheHandler::invalidate(int $schemaId)clears the in-process schema cache + the request-scopedSchemaMapper::findCacheentry. The next read in the same PHP worker re-fetches from the DB.RegisterCacheHandler::invalidate(int $registerId)does the same for registers (registers don't have a persistent cache table; the request-scoped mapper cache is the relevant clearance target).
This closes the "create schema → immediately use it" loop that runtime authoring requires.
DELETE safety
DELETE /api/schemas/{id}
DELETE /api/registers/{id}
Without ?force=true, the controller refuses to delete an entity that
still owns objects: returns HTTP 409 with body
{ "error": "schema-has-objects", "objectCount": N } (or
register-has-objects on registers). With ?force=true, the delete
proceeds and a WARNING is logged with the calling user, the entity
slug, and the orphan-object count for audit-trail review.
importFromApp auto-Register
When a configuration imported via ImportHandler::importFromApp
carries x-openregister.type=application, the handler now derives a
Register entity from x-openregister.app (slug), info.title
(title), and info.description (description). Lookup is idempotent
on (slug, organisationId) so re-imports update rather than
duplicate; schemas[] is unioned so already-attached schemas are
preserved across re-imports. Configurations of any other x-openregister.type
or without the marker are NOT auto-provisioned — the previous
behaviour is preserved.
Slug-aware search helper
$results = $objectService->searchObjectsBySlug(
'openbuild', // register slug
'application', // schema slug
['type' => 'service']
);
ObjectService::searchObjects requires numeric @self.register and
@self.schema for the fast path; passing slugs silently downgraded
the search before this spec landed. searchObjectsBySlug() resolves
both slugs through the mappers' standard organisation-filtered
find() and delegates to searchObjects(). An unknown slug
(either side) throws OCP\AppFramework\Db\DoesNotExistException
identifying which side failed; foreign-organisation slugs throw
the same exception (the resolver never crosses tenants).
Smoke test reference
The bootstrap-openbuild smoke test (commit 3138e4c, openbuild repo)
documents the manual flow this spec was driven from. Re-running it
against this branch confirms:
POST /api/configurations/import-from-appof anapplication-type OAS produces a complete(Configuration, Schemas, Register)triple in one call — no manualPOST /api/registersneeded.searchObjectsBySlug('openbuild', 'application', [])returns the seeded objects (pre-spec: zero results).- Editing a schema via
PUT /api/schemas/{id}then re-listing immediately surfaces the new state in the same PHP worker (cache invalidation works).