Consumers query Norwegian legal metadata offline through Get-LovdataDocument, Find-LovdataDocument, and Get-LovdataLegalArea, all of which read the bundled index. Bundling the whole corpus rather than a small list was the main design risk, and the lazy-load design handles it well: nothing is read at import, so a caller using only Get-LovdataPublicDataset or Test-LovdataConnection never pays for the corpus, and the packaged artifact is trivial.
Request
User impact
The first index query — the moment a caller touches any of the three document commands — costs roughly 2.3 seconds and a one-time jump of about 254 MB resident memory, regardless of what the caller asked for. Import-LovdataIndexData reads every source file and materialises all 4,185 records as LovdataDocument instances up front, then caches them for the session. Subsequent queries are fast (~22 ms). A quarter of a gigabyte of live objects is material in CI runners, containers, and constrained hosts, and a caller querying only acts (nl) still pays for sf, del, ins, and stv.
Measurements taken by building the module locally and copying the index to the module root as Build-PSModuleBase does:
| Metric |
Value |
| Index JSON on disk |
9.94 MB across 5 files |
| Built module folder |
10.03 MB |
| Packaged (zip/nupkg) |
1.21 MB |
Import-Module cold |
50 ms |
| Memory after import |
91 MB |
| First index query |
2,319 ms |
| Memory after first query |
345 MB |
| Subsequent query |
22 ms |
Find-LovdataDocument -Name 'veg' |
89 ms, 112 hits |
The nl source is 1.87 MB of the 9.94 MB total, so a caller who only needs acts currently loads roughly five times more data and memory than the query requires. Constructing 4,185 full class instances is what dominates the memory: a search returning 112 hits does not need 4,185 constructed objects to exist first.
Acceptance criteria
- A caller can restrict a query to the sources it needs, and doing so loads and constructs only those sources.
- The default behaviour is unchanged for callers that pass no source, so nothing regresses.
- First-query time and peak memory drop measurably for source-scoped queries, and ideally for the whole-index case as well.
- No change to the shipped index files or to the object shape callers already receive.
Regression
Not a regression. This is the behaviour since the index was introduced in #18. Deferred from the review of #20 to keep that correctness fix (per-reference LastChangedBy dates) landing clean on its own.
Technical decisions
Two independent improvements, either deliverable on its own.
1 — Load only the sources needed. Import-LovdataIndexData (in src/functions/private/Parsing/) takes no source parameter today; it globs LovdataIndex.*.json and reads all of them into one session cache. Add an optional Source parameter that selects which per-source files to read, defaulting to all so existing callers are unaffected. Thread an equivalent selector through the three public commands so a caller can scope a query. The cache becomes per-source rather than a single all-or-nothing blob. Open decision: whether the source selector is surfaced on the public commands as a parameter with a ValidateSet of the known bases (nl, sf, del, ins, stv) or discovered from the shipped files at runtime — discovery avoids hardcoding but loses tab-completion.
2 — Defer LovdataDocument construction. The loader currently calls [LovdataDocument]::new(...) for every record eagerly. Keep the deserialised records and construct a LovdataDocument only for the records a query actually returns. This preserves the [LovdataDocument] output contract the commands and tests assert while removing the up-front construction of thousands of objects that are never returned.
Breaking changes: None. Both changes are additive; default output type and default behaviour are preserved.
Test approach: Assert that a source-scoped query does not read or construct other sources (spy on file reads / count constructed instances), that the default path still returns every document, and that the output remains [LovdataDocument].
Implementation plan
Core changes
Tests
Documentation
Consumers query Norwegian legal metadata offline through
Get-LovdataDocument,Find-LovdataDocument, andGet-LovdataLegalArea, all of which read the bundled index. Bundling the whole corpus rather than a small list was the main design risk, and the lazy-load design handles it well: nothing is read at import, so a caller using onlyGet-LovdataPublicDatasetorTest-LovdataConnectionnever pays for the corpus, and the packaged artifact is trivial.Request
User impact
The first index query — the moment a caller touches any of the three document commands — costs roughly 2.3 seconds and a one-time jump of about 254 MB resident memory, regardless of what the caller asked for.
Import-LovdataIndexDatareads every source file and materialises all 4,185 records asLovdataDocumentinstances up front, then caches them for the session. Subsequent queries are fast (~22 ms). A quarter of a gigabyte of live objects is material in CI runners, containers, and constrained hosts, and a caller querying only acts (nl) still pays forsf,del,ins, andstv.Measurements taken by building the module locally and copying the index to the module root as
Build-PSModuleBasedoes:Import-ModulecoldFind-LovdataDocument -Name 'veg'The
nlsource is 1.87 MB of the 9.94 MB total, so a caller who only needs acts currently loads roughly five times more data and memory than the query requires. Constructing 4,185 full class instances is what dominates the memory: a search returning 112 hits does not need 4,185 constructed objects to exist first.Acceptance criteria
Regression
Not a regression. This is the behaviour since the index was introduced in #18. Deferred from the review of #20 to keep that correctness fix (per-reference
LastChangedBydates) landing clean on its own.Technical decisions
Two independent improvements, either deliverable on its own.
1 — Load only the sources needed.
Import-LovdataIndexData(insrc/functions/private/Parsing/) takes no source parameter today; it globsLovdataIndex.*.jsonand reads all of them into one session cache. Add an optionalSourceparameter that selects which per-source files to read, defaulting to all so existing callers are unaffected. Thread an equivalent selector through the three public commands so a caller can scope a query. The cache becomes per-source rather than a single all-or-nothing blob. Open decision: whether the source selector is surfaced on the public commands as a parameter with aValidateSetof the known bases (nl,sf,del,ins,stv) or discovered from the shipped files at runtime — discovery avoids hardcoding but loses tab-completion.2 — Defer
LovdataDocumentconstruction. The loader currently calls[LovdataDocument]::new(...)for every record eagerly. Keep the deserialised records and construct aLovdataDocumentonly for the records a query actually returns. This preserves the[LovdataDocument]output contract the commands and tests assert while removing the up-front construction of thousands of objects that are never returned.Breaking changes: None. Both changes are additive; default output type and default behaviour are preserved.
Test approach: Assert that a source-scoped query does not read or construct other sources (spy on file reads / count constructed instances), that the default path still returns every document, and that the output remains
[LovdataDocument].Implementation plan
Core changes
Sourceparameter toImport-LovdataIndexDatainsrc/functions/private/Parsing/, defaulting to all sources, with a per-source cacheGet-LovdataDocument,Find-LovdataDocument, andGet-LovdataLegalAreaLovdataDocumentconstruction to the records actually returned rather than constructing the whole index up frontTests
LovdataDocumentDocumentation
Documents.mdwith the source parameter