survos / folio-bundle
Portable SQLite folios for normalized museum dataset rows.
Fund package maintenance!
Requires
- php: ^8.5
- ext-pdo_sqlite: *
- ext-sqlite3: *
- doctrine/dbal: ^4.4
- doctrine/doctrine-bundle: ^2.14|^3.0
- doctrine/orm: ^3.7
- league/commonmark: ^2.4
- phpdocumentor/reflection-docblock: ^5.6 || ^6.0
- survos/babel-bundle: ^2.10
- survos/data-contracts: ^2.5
- survos/dimensions-bundle: ^2.10
- survos/field-bundle: ^2.5
- survos/folio: ^2.35.13
- survos/image-contracts: ^2.5
- survos/jsonl-bundle: ^2.5
- survos/kit-bundle: ^2.33
- survos/search-bundle: ^2.45.7
- symfony/ai-agent: ^1.0.0-beta1
- symfony/config: ^8.1
- symfony/console: ^8.1
- symfony/dependency-injection: ^8.1
- symfony/framework-bundle: ^8.1
- symfony/http-client: ^8.1
- symfony/http-kernel: ^8.1
- symfony/intl: ^8.1
- symfony/lock: ^8.1
- symfony/property-info: ^8.1
- symfony/routing: ^8.1
- symfony/ux-pagination: ^3.5.1
- symfony/ux-router: ^3.6
- tacman/gcf-php: ^1.0
- twig/string-extra: ^3.20
- twig/twig: ^3.24|^4.0
- voku/stop-words: ^2.0
- zenstruck/bytes: ^1.1
Requires (Dev)
- api-platform/core: ^5.0
- phpstan/phpstan: ^2.1
- survos/bookmark-bundle: ^2.34 || dev-main
- survos/duckdb-bundle: ^2.45
- survos/fetch-bundle: ^2.5
- survos/iiif-bundle: ^2.5
- survos/imgproxy-bundle: ^2.5
- survos/tabler-bundle: ^2.5
- symfony/ux-chartjs: ^3.0
Suggests
- ext-pdo_duckdb: Build optional local DuckDB 2.0 analytical snapshots from explicit JSONL inputs.
- api-platform/core: Required for the PhotoGrid Twig component's data source (Row's #[ApiResource]).
- presta/sitemap-bundle: For folio:sitemap — static per-folio XML sitemaps of item pages.
- survos/bookmark-bundle: Required when bookmark_class and folder_class are configured; install and configure the bookmark owner explicitly.
- survos/duckdb-bundle: Pool opened DuckDB files across a request or worker lifetime and show them in the profiler.
- survos/fetch-bundle: Provides ChunkDownloader for HTTP folio:pull downloads.
- survos/imagor-bundle: Image backend for |folio_image and the API thumbnails (or imgproxy-bundle).
- survos/imgproxy-bundle: Image backend for |folio_image and the API thumbnails (or imagor-bundle); without one, source URLs are used as-is.
- survos/import-bundle: Produces normalized JSONL files before ingesting them into folios.
- survos/schema-org-bundle: Publishes JSON-LD on the row detail page (RowSchemaOrgBuilder), reading the #[SchemaOrg] attributes survos/data-contracts already puts on the item DTOs. Without it the row page has no structured data.
- survos/settings-bundle: Lets RowMenu gate OCR/Handwriting behind a per-user beta_features setting instead of only the dev environment.
- symfony/ux-leaflet-map: Draws the folio map page (survos_folio_map); without it the page explains what is missing.
- symfony/ux-twig-component: Required for the PhotoGrid Twig component.
Provides
None
Conflicts
None
Replaces
None
- dev-main
- 2.45.31
- 2.45.30
- 2.45.29
- 2.45.28
- 2.45.27
- 2.45.26
- 2.45.25
- 2.45.23
- 2.45.22
- 2.45.19
- 2.45.18
- 2.45.16
- 2.45.15
- 2.45.14
- 2.45.11
- 2.45.10
- 2.45.7
- 2.45.6
- 2.45.5
- 2.45.3
- 2.45.1
- 2.44.0
- 2.43.21
- 2.43.14
- 2.43.12
- 2.43.11
- 2.43.7
- 2.43.6
- 2.43.5
- 2.43.3
- 2.43.2
- 2.43.0
- 2.42.15
- 2.42.14
- 2.42.5
- 2.42.4
- 2.42.2
- 2.42.1
- 2.42.0
- 2.41.9
- 2.41.2
- 2.41.1
- 2.41.0
- 2.40.0
- 2.39.0
- 2.37.6
- 2.37.5
- 2.37.3
- 2.37.0
- 2.36.4
- 2.36.3
- 2.36.2
- 2.36.1
- 2.36.0
- 2.35.23
- 2.35.22
- 2.35.21
- 2.35.20
- 2.35.19
- 2.35.17
- 2.35.16
- 2.35.15
- 2.35.14
- 2.35.13
- 2.35.10
- 2.35.9
- 2.35.8
- 2.35.7
- 2.35.5
- 2.35.3
- 2.35.2
- 2.34.38
- 2.34.33
- 2.34.27
- 2.34.16
- 2.34.13
- 2.34.12
- 2.34.11
- 2.34.10
- 2.34.9
- 2.34.8
- 2.34.7
- 2.34.6
- 2.34.5
- 2.34.4
- 2.34.3
- 2.34.2
- 2.34.1
- 2.33.3
- 2.33.1
- 2.33.0
- 2.32.0
- 2.31.17
- 2.31.16
- 2.31.13
- 2.31.12
- 2.31.10
- 2.31.6
- 2.31.3
- 2.31.1
- 2.31.0
- 2.30.11
- 2.30.10
- 2.30.9
- 2.30.7
- 2.30.5
- 2.30.3
- 2.30.1
- 2.30.0
- 2.29.2
- 2.29.0
- 2.28.7
- 2.28.5
- 2.28.4
- 2.28.3
- 2.27.1
- 2.26.9
- 2.24.38
- 2.24.37
- 2.24.33
- 2.24.32
- 2.24.31
- 2.24.30
- 2.24.24
- 2.24.22
- 2.24.19
- 2.24.18
- 2.24.17
- 2.24.10
- 2.24.0
- 2.23.1
- 2.23.0
- 2.22.0
- 2.21.0
- 2.19.5
- 2.19.4
- 2.19.3
- 2.19.2
- 2.18.19
- 2.18.18
- 2.18.17
- 2.18.14
- 2.18.13
- 2.18.12
- 2.18.11
- 2.18.10
- 2.18.9
- 2.18.7
- 2.18.5
- 2.18.3
- 2.18.2
- 2.18.1
- 2.18.0
- 2.17.6
- 2.17.5
- 2.17.0
- 2.16.2
- 2.16.0
- 2.15.22
- 2.15.21
- 2.15.20
- 2.15.19
- 2.15.18
- 2.15.17
- 2.15.16
- 2.15.15
- 2.15.14
- 2.15.13
- 2.15.12
- 2.15.10
- 2.15.9
- 2.15.8
- 2.15.7
- 2.15.6
- 2.15.5
- 2.15.4
- 2.15.2
- 2.15.1
- 2.14.3
- 2.14.1
- 2.13.0
- 2.12.12
- 2.12.11
- 2.12.9
- 2.12.8
- 2.12.7
- 2.12.6
- 2.12.5
- 2.12.4
- 2.12.3
- 2.12.1
- 2.12.0
- 2.11.8
- 2.11.7
- 2.11.6
- 2.11.4
- 2.11.3
- 2.11.2
- 2.11.1
- 2.11.0
- 2.10.31
- 2.10.30
- 2.10.29
- 2.10.28
- 2.10.27
- 2.10.26
- 2.10.25
- 2.10.24
- 2.10.22
- 2.10.21
- 2.10.20
- 2.10.19
- 2.10.18
- 2.10.17
- 2.10.16
- 2.10.15
- 2.10.14
- 2.10.13
- 2.10.12
- 2.10.11
- 2.10.10
- 2.10.9
- 2.10.8
- 2.10.7
- 2.10.6
- 2.10.5
- 2.10.4
- 2.10.3
- 2.10.2
- 2.10.1
- 2.10.0
- 2.9.4
- 2.9.3
- 2.9.2
- 2.9.1
- 2.9.0
- 2.8.4
- 2.8.3
- 2.8.2
- 2.8.1
- 2.8.0
- 2.7.23
- 2.7.22
- 2.7.21
- 2.7.20
- 2.7.19
- 2.7.18
- 2.7.17
- 2.7.16
- 2.7.15
- 2.7.14
- 2.7.13
- 2.7.12
- 2.7.11
- 2.7.10
- 2.7.9
- 2.7.8
- 2.7.7
- 2.7.6
- 2.7.5
- 2.7.4
- 2.7.3
- 2.7.2
- 2.7.1
- 2.7.0
- 2.6.0
- 2.5.8
- 2.5.7
- 2.5.6
- 2.5.5
- 2.5.3
- 2.5.2
- 2.5.1
- 2.5.0
- 2.4.4
- 2.4.3
- 2.4.2
- 2.4.1
- 2.4.0
- 2.3.0
This package is auto-updated.
Last update: 2026-11-09 12:33:45 UTC
README
Folio stores normalized/enriched dataset JSONL as portable SQLite archive files. It is the database, archive, and browsing layer for data that has already been normalized by dataset/import tooling.
FolioBuilder turns explicit JSONL inputs into a standalone folio SQLite file. The owning application selects inputs and manages its workflow. Consumers such as zm can use the Symfony/DataContracts stack when present, while Python/R/SQLite users can query the archive directly.
Required: survos/field-bundle, survos/data-contracts.
Suggested for ingest/write workflows: survos/jsonl-bundle, survos/import-bundle.
Folios live under survos_folio.data_dir (default APP_DATA_DIR); an application's own DataPaths service, if any, wins.
See docs/configuration.md for paths and the required multi-connection Doctrine setup.
See docs/archive-metadata.md for the standalone archive metadata contract.
See docs/publisher-registration.md for how a publishing app confirms and withdraws folios with Harvest (PublisherRegistrar).
See docs/presentation-layer.md for the proposal to use folios as narrative institutional presentation packages.
Building and command cleanup
See Building folios for the service API and supported commands. Dataset-specific build, archive, translate and ingest CLI shortcuts are removed; no replacement shortcuts are installed in another bundle.
Archive Contract
A folio file stores canonical rows in item and self-describing metadata alongside them:
schema_tableandschema_propertydescribe observed DTO types and fields in this archive.schema_property.statsstores field profile output fromsurvos/jsonl-bundle's profiler.docsstores generated JSON/Markdown documentation for humans, report writers, and AI agents.- generated
dto_*SQLite views project JSON fields into query-friendly columns. term_setandtermstore standalone controlled vocabularies and facets.
The metadata snapshot describes actual observed data, not the entire DTO contract universe. DTO classes from survos/data-contracts annotate observed fields with labels/descriptions when available, but consumers do not need PHP code to understand an archived folio.
Search and Publication Notes
FolioBuilderloads rows, snapshots observed schema/docs/views, and rebuilds the SQLite FTS5 tableitem_fts.- Existing folios can rebuild search with
bin/console folio:fts:rebuild <provider/dataset> --query="search terms". FolioArchiveServicerefreshes archive metadata before packaging.- FTS tables are derived data. Published archive files may drop
item_fts,VACUUM, compress, ship, then rebuild FTS on the consuming side. - SQLite views and docs are also derived from persisted metadata, but they are intentionally lightweight and useful for standalone consumers.
- Vector search is intentionally deferred. When added, start with a hybrid SQLite design: FTS5/BM25 for exact keyword strength, sqlite-vec for semantic retrieval, and Reciprocal Rank Fusion to merge ranks without normalizing incompatible score scales. Reference: https://ceaksan.com/en/hybrid-search-fts5-vector-rrf
Direct SQLite Examples
select * from schema_table where kind = 'dto'; select * from schema_property where table_id = ? order by position; select local_id, label, dto_type, dto_data, extras from item limit 20; select * from dto_document limit 20; select id, type, audience, body from docs order by position;
TODO
- Add fieldSet support to the api-grid spreadsheet view to avoid displaying every DTO field at once.
- Rebuild views/docs on restore, not only FTS, if the archive was packaged without them.
Catalog consumers and published shared folios
survos_folio.read_only: true opens FolioService contexts using SQLite mode=ro, disables
schema auto-migration/folio-row creation and avoids WAL mode changes. Publish a compatible, completed,
checkpointed folio before sharing; the reader will not upgrade it. Explicit archive restore
and inflate commands still write their selected local targets.
archive_api_prefix defaults to /folio and controls folio:pull listing/fallback download
URLs independently of this app's route_prefix or a remote /f browse prefix. Download URLs
returned by the catalog remain authoritative. local_passthrough: true skips already-present
shared folios even with --force.
PeriodicalCoverageService::compute() preserves periodical block/article/ad distinctions
and returns counts plus per-issue details. It scans folio rows: run it offline when recording
catalog summaries, never as a directory-page cache-miss fallback. zm's publisher command
persists the compact versioned summary in its registry; Ink consumes that recorded API data.
Catalog UI
The former dataset-registry collection controller and grid are removed. Applications
render published catalog entries using FolioCatalogClient; the demo shows this
without a dataset database. Reader routes for individual folios remain available.
Prepare a queryable DuckDB companion
DuckDbFolioPreparer::prepare($folioPath, fts: true) derives a local .duckdb
from an already restored .folio, without running Harvest ingestion. Restore
archives with the existing FolioPullCommand / FolioArchiveService first.
The optional target defaults to the sibling .duckdb path.
The preparer reuses DuckDbFolioExporter, then adds search_records via
DuckDbFolioSearchProjection and optional persisted FTS via search-bundle's
DuckDbFtsIndexer. Native DTO columns remain authoritative; this export still
mirrors the archive's logical tables as well. It is not a lean export profile.
_duckdb_preparation records the shared build ID, schema/version signature and
FTS choice. Matching files are reused. Changes rebuild under a per-file local
lock into a staging file and replace the target only after a checkpoint.
Failures preserve the previously published companion. force: true rebuilds
regardless of its signature. No PDO connection remains open after preparation.
Native PDO DuckDB 2.x is required; install the FTS extension beforehand when used.
Older folios without _folio_build are rejected rather than assigned an invented
identity. ORM access through FolioContext is unchanged.
Newspaper DuckDB geometry
Newspaper .dfolio export requires a rendition publisher callback. One published
DuckDB .drend per paper holds every issue's page layouts/dimensions and nested
coordinate patches; _renditions pins its URI, size and source build. Stable anchors,
text and semantic grouping remain in .dfolio. Every source item/page must map
to an issue. Non-newspaper exports retain the previous complete-table behavior.
DuckDbRenditionReader validates the namespace, issue and build before restoring
exact geometry. DuckDbFolioRowMapper constructs the existing detached Row/Page
model. Full DuckDbFolioComparator comparison of a newspaper pair requires an
openRend callback and compares restored values, not counts alone. Remote readers
attach both files read-only with HTTP ranges. S3 secrets are temporary in the
reader's isolated in-memory database, including the internal WAL existence probe;
no credentials are written into either snapshot.
Rendition connections are pooled by file/endpoint and invalidated by build ID,
not opened again for each issue. Every issue-bearing query must include an
explicit WHERE issue = ?; joins between blocks and crops must also match
issue, since their IDs repeat across issues. No issue-specific temporary views
are installed on shared connections. restore($rend, $items, $pages, $issueId)
requires the issue explicitly; validateIssue() replaces the old scope() API.
Consumers using the old views must migrate with this reader change. The shared
browse view and comparator use explicit issue filters too. Without DuckDbBundle,
the reader keeps a bounded two-file cache with the same build invalidation.
Typed ordering and grouping counts
Exports materialize link.ordinal as nullable BIGINT from the retained link
metadata when the source has no typed ordinal. Ordering can use
ORDER BY l.ordinal, i.local_id without parsing JSON. This is stable relationship
ordering, not an intrinsic order assigned to every item. Original extras remain
unchanged; a source's existing typed ordinal takes precedence.
Newspaper exports also materialize issue_group_role: issue/page, group ordinal
and ID, numeric role ordinal, role, block ID, and start/end line offsets. This
lets counts join issue_grouping, issue_page, issue_group, and role spans to
rendition OCR without traversing grouping JSON. Empty newspaper projections are
created too. Issues with grouping history retain that history in issue_extras
and participate in the typed grouping projection.
Re-export and republish existing newspaper snapshots before deploying consumers that require these columns/tables. Rebuild from the existing source folio (or the normal scratch-folio build from normalized inputs), not by running ALTER against published S3 files. No new OCR, grouping algorithm, or source schema migration is required. Consumers must synchronize the new catalog revision.