You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Task ID: A4 Title: Issue #167 — Pagination-Contract JSDoc on List Methods Source: Repo issue #167 Severity: P3-Minor Category: Documentation / API Design Ownership: Ours (CSAPI module — csapi/url_builder.ts) Phase 8 phase: A — Documentation & Type-Hardening
Goal
Every list method on CSAPIQueryBuilder carries JSDoc that explicitly documents the pagination contract: server picks the default page size, and the consumer follows next HATEOAS links to retrieve subsequent pages. A centralized "Pagination" doc anchor on the module/class docblock is the single source of truth; per-method @remarks blocks point at it.
⚠️ This decision is locked. Do not re-litigate in this issue. Surface deviations to the user; do not silently re-decide.
Decision: Adopt the docs-only fix from issue #167 — add a centralized "Pagination" anchor in the csapi/url_builder.ts module/class docblock, plus a @remarks block on every list method that points at the anchor. Do NOT add an auto-pagination helper or async-iterator in Phase 8 — those are deferred to issue #170 and explicitly out of scope here.
The @remarks block must call out, in plain language, that:
The server picks the default limit (connected-systems-go defaults to 10; OpenSensorHub defaults to 100 — concrete servers cited).
The consumer must follow rel: "next" HATEOAS links to retrieve subsequent pages.
Issue #170 — deferred enhancement that this task explicitly does NOT pre-empt
Problem Statement
connected-systems-go defaults to limit=10; OpenSensorHub defaults to limit=100. A consumer who only tested against the high-default server may silently process only the first page in production against a low-default server. The OGC API Common spec lets servers choose the default; the library's job is to make that contract impossible to miss in the JSDoc.
Affected code:src/ogc-api/csapi/url_builder.ts — every list method (getSystems, getDeployments, getProcedures, getSamplingFeatures, getDatastreams, getDatastreamObservations, getDatastreamSystems, getDatastreamProcedures, getDatastreamHistory, getSystemDatastreams, getSystemSubsystems, getProcedureDatastreams, getControlStreams, getCommands, getCommandStatus if it returns a list, getObservations if exposed) currently lacks the explicit pagination contract in its JSDoc.
Impact: Silent data loss in consumer code that processes only the first page. Pure DX/correctness-of-usage gap; no library-side runtime bug.
Files to Modify
File
Action
Est. Lines
Purpose
src/ogc-api/csapi/url_builder.ts
Modify
~80
Class/module-level "Pagination" doc anchor + @remarks Pagination block on each list method (~16 methods)
Cross-reference P8-implementation-guide §5.2 for the canonical files-modified list. If you find yourself adding a file that isn't in the implementation guide, stop — surface the question.
Centralized "Pagination" anchor (class/module docblock in csapi/url_builder.ts):
/** * ... * * ## Pagination * * All list methods (`get*` returning collection URLs) follow the * [OGC API Common](https://docs.ogc.org/is/19-072/19-072.html#_pagination) * pagination contract: * * - **The server chooses the default page size** if `limit` is unspecified. * Defaults vary by implementation — `connected-systems-go` defaults to * `limit=10`; OpenSensorHub defaults to `limit=100`. Code that processes * only the first response may silently lose data on low-default servers. * * - **The server returns `next` HATEOAS links** in the response body's * `links` array (`rel: "next"`) when more pages are available. The * consumer is responsible for following them; this library does not * auto-paginate. * * - **A future enhancement** (deferred — see issue * [#170](https://github.com/OS4CSAPI/ogc-client-CSAPI_2/issues/170)) * may add an opt-in async-iterator / `followNext` helper. Until then, * consumer code MUST follow `next` links explicitly to avoid data loss. * * ... */
Per-method @remarks tag — apply verbatim shape to every list method:
/** * Builds the URL for a paginated list of datastreams. * * @remarks * **Pagination:** server picks the default `limit` if unspecified; the * consumer must follow `next` HATEOAS links from the response body to * retrieve subsequent pages. See the * [Pagination section of this module's docs](#pagination). * * @param options - Optional query parameters (limit, bbox, datetime, etc.). * @returns The fully-formed URL string. */publicgetDatastreams(options?: DatastreamQueryOptions): string { ... }
Sequencing note: A4 should be executed after Task B1 (the Datastream rename) so JSDoc bodies are written against the final method names. If A4 runs before B1, the JSDoc will be re-touched in B1; mechanically valid but creates churn. Either order works; post-B1 is recommended.
Test impact: none for behavior. An optional snapshot/lint test asserting every public list method's JSDoc matches /Pagination:.*next.*links/i MAY be added if it does not increase friction; otherwise skip.
Scope — What NOT to Touch
❌ Do NOT modify files outside the "Files to Modify" table above
❌ Do NOT pre-emptively rename getDataStreams → getDatastreams outside this task's JSDoc body — that's Task B1's job. (If A4 lands before B1, write the JSDoc against current method names; B1 will rename them in lockstep.)
❌ Do NOT touch error-throwing patterns, availableResources typing, CSAPICollectionRef, or factory shape — those are A2/A3/B2/D1
Every public list method on CSAPIQueryBuilder carries a @remarks block with the Pagination note pointing at the anchor (target: ~16 methods after Task B1's renames)
All @remarks Pagination blocks use consistent wording (template above)
No source code changes (signatures, runtime behavior, return types) — docs only
All modified files pass npx prettier --check
npm run typecheck exits 0
npm run lint exits 0
npm run test:browser exits 0
npm run test:node exits 0
Acceptance Gate (verification command)
The Phase 8 roadmap defines a specific verification command for this task (P8-ROADMAP §Phase A Task A4). Paste the output of these commands on the issue before closing:
# 1. Manual review confirmation — paste a one-paragraph statement listing every# list method touched and confirming each carries the @remarks Pagination# block, plus citing the line range of the centralized anchor.# 2. Spot-check via grep — every list method should have a Pagination remark.
git grep -n "Pagination:"-- src/ogc-api/csapi/url_builder.ts
# Expected: one match per list method + 1 anchor heading# 3. Full upstream QA suite (must mirror .github/workflows/qa.yml)
npm run format:check
npm run typecheck
npm run lint
npm run test:browser
npm run test:node
Expected output:
git grep returns ≥ 16 matches (one per list method) plus the anchor section heading
Blocked by: Nothing hard. Soft dependency on Task B1 (the Datastream rename) — recommended sequencing is to run A4 after B1 so JSDoc bodies are written against final method names. Either order works mechanically; post-B1 placement avoids re-touching the same JSDoc blocks. Blocks: Nothing directly Related: Task B1 (the rename — see soft dependency above); Task A1 (the module docblock added there can cross-link to the Pagination anchor); Issue #170 (the deferred async-iterator helper that this task's "deferred enhancement" note must cite)
Roadmap dependency row: P8-ROADMAP §Phase A Task A4: "Effort: Small (~1.5 hours, mostly mechanical). Risk: None. Dependencies: Logically depends on Task B1 (the rename) for final method names, but the JSDoc bodies can be written first against the old names and renamed in lockstep with B1. Recommended to run A4 after B1 to avoid double-touching the same methods."
Original Finding (preserved from pre-Phase-8 issue body)
Below is the original investigation/assessment that motivated this Phase 8 task. Preserved verbatim — Phase 8 absorbs this issue in place rather than creating a wrapper.
📌 Status Update — 2026-04-28 (Phase 8 Triage)
This issue's diagnosis and proposed fix have been revised after deep evaluation. The original framing identified a real symptom (consumers receiving fewer results than expected on servers with low default page sizes) but misidentified the cause and recommended a fix that conflicts with both OGC spec philosophy and upstream precedent calibration.
Corrected diagnosis:
The library is behaving correctly per OGC 23-001 §7.6. When limit is omitted, the server's default applies. This is spec-conformant. The cited "data loss" only occurs when a consumer fails to follow the next HATEOAS link — i.e., consumes only the first page of a paginated response. That is a consumer-side pagination defect, not a library defect.
The cited consumer (find_by_uid() in OSHConnect-Python) implements single-page lookup and does not follow next links. Hardcoding &limit=1000 masks that defect; setting DEFAULT_LIMIT=100 in this library would only move the cutoff, not fix it. Any tenant with >100 systems would still fail.
However, there is a real library-side gap: our public list methods do not document the pagination contract in their JSDoc. A consumer reading our API has no signal that single-call list methods may return partial results, or that they should follow the next link to retrieve subsequent pages. That is a documentation completeness issue we own.
Upstream precedent calibration: Upstream getCollectionItems() (src/ogc-api/endpoint.ts:512) does set a client-side default of limit = 10 at the convenience-method layer (not the URL-builder layer). The lower-level getCollectionItemsUrl() keeps limit? optional with no default — same pattern as our buildQueryString(). So whether we also impose a client-side default on our public list methods is a separate, deferrable architectural decision; if we do, the upstream-aligned value is 10, not 100. This is recorded as an open question below — not part of the accepted scope.
Revised severity:P3-Suggestion (was P2-Important). The symptom requires three conditions to manifest: (a) caller omits limit, (b) caller does not follow next links, (c) server default page size is smaller than the result set. A spec-conformant consumer is unaffected. Compare to #166, where the bug silently corrupts parsed objects regardless of consumer behavior — that's genuine P1. This is "consumer that doesn't paginate has a problem," which is a different category.
Revised recommendation:Documentation-only fix — add JSDoc on every public list method explicitly stating the pagination contract (single page returned; consumers must follow next links to retrieve subsequent pages). No behavior change. No new code. ~30 lines of JSDoc additions across the affected methods.
Future enhancement (not in this issue's scope): An async-iterator helper (async function* iterateSystems()) that auto-follows next links would be the genuine ergonomic solution to the underlying user-experience problem. Upstream ogc-client does not provide this pattern (we audited — no async *, AsyncIterable, getAllItems, iterate, fetchAll matches anywhere in src/), so adding it would be a scope-broadening contribution. Per our governance model, that's appropriate to tee up in our queue and revisit only if upstream broadens scope. To be filed as a separate issue with explicit reasoning that (a) it has no upstream precedent, (b) we'd want upstream to lead, (c) we'd revisit if upstream takes the direction.
Out-of-scope follow-up (not our repo): The cited find_by_uid() defect lives in OS4CSAPI/OSHConnect-Python, not here. If that project wants to fix it, that's a separate conversation against that repo.
The original problem statement and proposed fixes are preserved below for audit. Items rendered with strikethrough are superseded by the corrected diagnosis above.
— see also: institutional-learning comment below for analysis of how this gap escaped 6 months of review.
Acceptance Criteria — Revised
Every public list method on CSAPIQueryBuilder (getSystems, getDataStreams, getObservations, getDeployments, getProcedures, getProperties, getControlStreams, getCommands, all get*History, get*Subsystems, get*Observations, etc.) has a JSDoc paragraph explicitly stating:
The method returns a URL for a single page of results.
The server applies its own default page size when limit is omitted (varies by implementation).
Consumers needing the full result set must follow the next HATEOAS link from the response.
One short note added to the CSAPI module-level documentation (e.g., src/ogc-api/csapi/index.ts or equivalent file we own) summarizing the pagination contract and pointing at the link-walking pattern used in our integration tests (e.g., src/ogc-api/csapi/integration/observation.spec.ts:253-267).
QueryOptions.limit JSDoc is expanded to clarify: "Optional. When omitted, the server applies its own default page size, which varies by implementation. To retrieve all results, follow the next link in the response."
No code/behavior changes. No public-API signature changes.
All modified files pass npx prettier --check. No lint or typecheck regressions.
Existing tests still pass.
Files to Modify — Revised
File
Action
Est. Lines
Purpose
src/ogc-api/csapi/url_builder.ts
JSDoc only
~30
Add pagination-contract paragraph to each public list method
src/ogc-api/csapi/model.ts
JSDoc only
~3
Expand QueryOptions.limit doc
Module index (e.g., src/ogc-api/csapi/index.ts)
JSDoc only
~10
Module-level pagination-contract note
Scope — What NOT to Touch
❌ Do NOT modify buildQueryString() behavior. Do NOT add DEFAULT_LIMIT.
❌ Do NOT modify validateLimit().
❌ Do NOT add any new code, new methods, or new constants.
❌ Do NOT modify the upstream-owned README.md.
❌ Do NOT add auto-pagination / fetch-all logic — that's a separate scope-broadening concern, to be filed as a follow-up enhancement issue (not addressed here).
Open Question — Defer to Phase 8 Execution Plan
Should our public list methods (getSystems, etc.) also apply a client-side default limit at the convenience-method layer, mirroring upstream getCollectionItems(limit = 10)? Arguments either way:
For: Mirrors upstream pattern. Reduces likelihood of consumers tripping the silent-truncation symptom on servers with very small defaults.
Against:CSAPIQueryBuilder is closer to a URL-builder than a convenience class (its buildQueryString counterpart in upstream — getCollectionItemsUrl — has no default). The whole class returns URL strings, not data; pagination concerns arguably belong at a higher layer the consumer composes.
Decision: Out of scope for this issue. If accepted later, the upstream-aligned value is 10, not 100. To be revisited during Phase 8 execution planning if appropriate, or filed as a separate follow-up.
Original Issue (Below — for audit; superseded items struck through)
Finding
CSAPIQueryBuilder.buildQueryString() only appends the limit query parameter when explicitly provided by the caller — when omitted, the server applies its own default page size, which varies across implementations (SensorHub: 100, Go CSAPI: 10). Consumers calling any list method without { limit: N } silently receive truncated results on servers with low defaults.
Review Source: Live integration testing against a Go CSAPI server (PostGIS-backed, OGC-compliant) from the ogc-csapi-explorer project and OSHConnect-Python publisher fleet migration. Severity:P2-Important → P3-Suggestion (see status update) Category:API Design → Documentation Ownership: Ours
Problem Statement
The OGC API — Connected Systems specification (OGC 23-001 §7.6) states that limit is an optional query parameter with a server-defined default. The spec does not mandate a minimum default — so any conformant server can return as few as 1 item per page if no limit is provided.
Our buildQueryString() method respects this literally: if the caller doesn't pass limit, no ?limit= appears in the URL, and the server picks its own default. This is technically correct but creates a practical interoperability trap — the library silently returns different result counts depending on which server it talks to, with no indication to the consumer that results were truncated.
Corrected (2026-04-28): This is technically correct and spec-conformant. The "interoperability trap" is real but is a documentation gap on our side (consumers aren't told to follow next links) plus a pagination defect in the cited consumer. The library is doing the right thing; our JSDoc just doesn't tell consumers what the right thing requires of them.
Affected code — buildQueryString() (line ~393):
// src/ogc-api/csapi/url_builder.ts — buildQueryString()}elseif(key==='limit'){validateLimit(value);params.append(wireName,String(value));}// When limit is NOT in options, no ?limit= is appended → server default applies
Scenario:
constbuilder=awaitcreateCSAPIBuilder(endpoint,collectionId);// Consumer expects "all systems" — gets only 10 on Go serverconsturl=builder.getSystems();// SensorHub: → .../systems (returns up to 100 — usually sufficient)// Go CSAPI: → .../systems (returns only 10 — silently truncated)// Consumer must know to do this, but nothing in the API signals it:constsafeUrl=builder.getSystems({limit: 100});
Corrected (2026-04-28): What the consumer must know is to follow the next link in the response (per OGC pagination contract), not to guess a high limit. Setting a high limit is a workaround that fails as soon as the result set exceeds it. The next-link pattern is exhibited in our own integration tests at src/ogc-api/csapi/integration/observation.spec.ts:253-267.
Impact:Any consumer that calls a list method without an explicit limit will silently receive truncated results when connected to a server whose default page size is smaller than the total resource count. This was the root cause of the find_by_uid() failures in the OSHConnect-Python publisher fleet — systems, datastreams, and deployments were not found because the Go server's default limit=10 returned only the first page. The workaround was to hardcode &limit=1000 in the Python bootstrap helpers.
Corrected (2026-04-28): Any consumer that (a) calls a list method without an explicit limit AND (b) does not follow next links receives only the first page. The find_by_uid() failure in OSHConnect-Python is a consumer-side pagination defect (single-page lookup, doesn't follow next); the hardcoded &limit=1000 workaround masks the consumer defect, it doesn't fix it. Tenants with >1000 systems would still fail. The fix for that consumer belongs in that consumer's repo, not ours.
All 39 public list methods that accept QueryOptions are affected, including:
All affected code is in our diff. The buildQueryString() method, CSAPIQueryBuilder class, QueryOptions interface, and validateLimit() helper were all authored as part of our CSAPI contribution. The upstream camptocamp/ogc-client has zero CSAPI code.
Conclusion: This code is ours.
Files to Modify — Original (Superseded)
File
Action
Est. Lines
Purpose
src/ogc-api/csapi/url_builder.ts
Modify
~~~10~~
Add configurable default limit with fallback constant
src/ogc-api/csapi/model.ts
Modify
~~~5~~
Add optional defaultLimit to builder config or QueryOptions JSDoc
src/ogc-api/csapi/url_builder.spec.ts
Modify
~~~20~~
Test that default limit is applied when limit is omitted
Superseded. See the revised "Files to Modify — Revised" table above.
Proposed Solutions — Original (Superseded)
Superseded by status update. Both Option A (constant DEFAULT_LIMIT = 100) and Option B (configurable via constructor) impose opinionated client-side behavior that contradicts OGC 23-001 §7.6's explicit delegation of default page size to the server. The chosen 100 is OSH-flavored — there is no principled reason to pick 100 over 10, 1000, or any other value. Even if we later decide to mirror upstream's getCollectionItems(limit = 10) pattern at our convenience layer (see "Open Question" above), neither original Option A nor Option B is the right shape for that decision. The original options are preserved below struck through for audit.
Add a DEFAULT_LIMIT constant to url_builder.ts and apply it in buildQueryString() when no explicit limit is provided:
/** Default page size applied when the caller omits `limit`. */constDEFAULT_LIMIT=100;privatebuildQueryString(options?: QueryOptions): string{// Apply default limit if caller didn't specify oneconsteffectiveOptions=options?.limit!==undefined
? options
: { ...options,limit: DEFAULT_LIMIT};// ... rest of existing logic, now always includes limit}
Pros: Minimal diff; consistent behavior across all servers; matches SensorHub's default (100); no API surface change — callers can still override with { limit: N } Cons: Adds a parameter the server may not expect (though limit is always a valid OGC parameter); opinionated default Effort: Small | Risk: Low
Option B: Configurable default via constructor option
Add an optional defaultLimit property to the CSAPIQueryBuilder constructor config:
interfaceCSAPIQueryBuilderOptions{// ... existing fields .../** Default limit applied to all list requests. Set to undefined to use server default. */defaultLimit?: number;}// In buildQueryString():if(!options?.limit&&this.defaultLimit_!==undefined){params.append('limit',String(this.defaultLimit_));}
Pros: Per-instance configurable; consumers can opt out by passing undefined; works well when the same builder talks to different servers Cons: Larger diff; requires factory function update; adds constructor complexity Effort: Medium | Risk: Low
Scope — Original (Superseded)
Superseded. See "Scope — What NOT to Touch" in the revised section above.
Acceptance Criteria — Original (Superseded)
Superseded. See "Acceptance Criteria — Revised" above.
Dependencies
Blocked by: Nothing Blocks: Nothing Related:#166 (Part 2 parsers @link fallback — same Go server interop effort); #110 (@link resolution utilities — higher-level pagination concern)
Phase 8 Task
Task ID: A4
Title: Issue #167 — Pagination-Contract JSDoc on List Methods
Source: Repo issue #167
Severity: P3-Minor
Category: Documentation / API Design
Ownership: Ours (CSAPI module —
csapi/url_builder.ts)Phase 8 phase: A — Documentation & Type-Hardening
Goal
Every list method on
CSAPIQueryBuildercarries JSDoc that explicitly documents the pagination contract: server picks the default page size, and the consumer followsnextHATEOAS links to retrieve subsequent pages. A centralized "Pagination" doc anchor on the module/class docblock is the single source of truth; per-method@remarksblocks point at it.Acceptance criterion (from P8-contribution-goal-and-definition.md): B2 — pagination contract documented on every list method.
Locked Decision
Decision: Adopt the docs-only fix from issue #167 — add a centralized "Pagination" anchor in the
csapi/url_builder.tsmodule/class docblock, plus a@remarksblock on every list method that points at the anchor. Do NOT add an auto-pagination helper or async-iterator in Phase 8 — those are deferred to issue #170 and explicitly out of scope here.The
@remarksblock must call out, in plain language, that:limit(connected-systems-godefaults to 10; OpenSensorHub defaults to 100 — concrete servers cited).rel: "next"HATEOAS links to retrieve subsequent pages.Locked in:
Problem Statement
connected-systems-godefaults tolimit=10; OpenSensorHub defaults tolimit=100. A consumer who only tested against the high-default server may silently process only the first page in production against a low-default server. The OGC API Common spec lets servers choose the default; the library's job is to make that contract impossible to miss in the JSDoc.Affected code:
src/ogc-api/csapi/url_builder.ts— every list method (getSystems,getDeployments,getProcedures,getSamplingFeatures,getDatastreams,getDatastreamObservations,getDatastreamSystems,getDatastreamProcedures,getDatastreamHistory,getSystemDatastreams,getSystemSubsystems,getProcedureDatastreams,getControlStreams,getCommands,getCommandStatusif it returns a list,getObservationsif exposed) currently lacks the explicit pagination contract in its JSDoc.Impact: Silent data loss in consumer code that processes only the first page. Pure DX/correctness-of-usage gap; no library-side runtime bug.
Files to Modify
src/ogc-api/csapi/url_builder.ts@remarksPagination block on each list method (~16 methods)Implementation Approach
Pull the canonical sketches from P8-implementation-guide §5.2.
Centralized "Pagination" anchor (class/module docblock in
csapi/url_builder.ts):Per-method
@remarkstag — apply verbatim shape to every list method:Sequencing note: A4 should be executed after Task B1 (the
Datastreamrename) so JSDoc bodies are written against the final method names. If A4 runs before B1, the JSDoc will be re-touched in B1; mechanically valid but creates churn. Either order works; post-B1 is recommended.Test impact: none for behavior. An optional snapshot/lint test asserting every public list method's JSDoc matches
/Pagination:.*next.*links/iMAY be added if it does not increase friction; otherwise skip.Scope — What NOT to Touch
followNextmethod (deferred to Future-enhancement (deferred): async-iterator helpers for paginated CSAPI list methods — out-of-scope until upstream broadens scope #170 — explicitly rejected for Phase 8)@deprecatedtags anywhere in Phase 8 (locked decision; PR unmerged ⇒ no consumers)getDataStreams→getDatastreamsoutside this task's JSDoc body — that's Task B1's job. (If A4 lands before B1, write the JSDoc against current method names; B1 will rename them in lockstep.)availableResourcestyping,CSAPICollectionRef, or factory shape — those are A2/A3/B2/D1Acceptance Criteria
src/ogc-api/csapi/url_builder.tscovering: server-picks-default contract, concrete defaults (CS-Go=10, OSH=100),next-link-follow responsibility, deferred-helper note citing Future-enhancement (deferred): async-iterator helpers for paginated CSAPI list methods — out-of-scope until upstream broadens scope #170CSAPIQueryBuildercarries a@remarksblock with the Pagination note pointing at the anchor (target: ~16 methods after Task B1's renames)@remarksPagination blocks use consistent wording (template above)npx prettier --checknpm run typecheckexits 0npm run lintexits 0npm run test:browserexits 0npm run test:nodeexits 0Acceptance Gate (verification command)
The Phase 8 roadmap defines a specific verification command for this task (P8-ROADMAP §Phase A Task A4). Paste the output of these commands on the issue before closing:
Expected output:
git grepreturns ≥ 16 matches (one per list method) plus the anchor section headingDependencies
Blocked by: Nothing hard. Soft dependency on Task B1 (the
Datastreamrename) — recommended sequencing is to run A4 after B1 so JSDoc bodies are written against final method names. Either order works mechanically; post-B1 placement avoids re-touching the same JSDoc blocks.Blocks: Nothing directly
Related: Task B1 (the rename — see soft dependency above); Task A1 (the module docblock added there can cross-link to the Pagination anchor); Issue #170 (the deferred async-iterator helper that this task's "deferred enhancement" note must cite)
Roadmap dependency row: P8-ROADMAP §Phase A Task A4: "Effort: Small (~1.5 hours, mostly mechanical). Risk: None. Dependencies: Logically depends on Task B1 (the rename) for final method names, but the JSDoc bodies can be written first against the old names and renamed in lockstep with B1. Recommended to run A4 after B1 to avoid double-touching the same methods."
References
src/ogc-api/csapi/url_builder.ts@remarksblocksOriginal Finding (preserved from pre-Phase-8 issue body)
Below is the original investigation/assessment that motivated this Phase 8 task. Preserved verbatim — Phase 8 absorbs this issue in place rather than creating a wrapper.
Acceptance Criteria — Revised
CSAPIQueryBuilder(getSystems,getDataStreams,getObservations,getDeployments,getProcedures,getProperties,getControlStreams,getCommands, allget*History,get*Subsystems,get*Observations, etc.) has a JSDoc paragraph explicitly stating:limitis omitted (varies by implementation).nextHATEOAS link from the response.src/ogc-api/csapi/index.tsor equivalent file we own) summarizing the pagination contract and pointing at the link-walking pattern used in our integration tests (e.g.,src/ogc-api/csapi/integration/observation.spec.ts:253-267).QueryOptions.limitJSDoc is expanded to clarify: "Optional. When omitted, the server applies its own default page size, which varies by implementation. To retrieve all results, follow thenextlink in the response."npx prettier --check. No lint or typecheck regressions.Files to Modify — Revised
src/ogc-api/csapi/url_builder.tssrc/ogc-api/csapi/model.tsQueryOptions.limitdocsrc/ogc-api/csapi/index.ts)Scope — What NOT to Touch
buildQueryString()behavior. Do NOT addDEFAULT_LIMIT.validateLimit().README.md.Open Question — Defer to Phase 8 Execution Plan
Should our public list methods (
getSystems, etc.) also apply a client-side defaultlimitat the convenience-method layer, mirroring upstreamgetCollectionItems(limit = 10)? Arguments either way:CSAPIQueryBuilderis closer to a URL-builder than a convenience class (itsbuildQueryStringcounterpart in upstream —getCollectionItemsUrl— has no default). The whole class returns URL strings, not data; pagination concerns arguably belong at a higher layer the consumer composes.Decision: Out of scope for this issue. If accepted later, the upstream-aligned value is
10, not100. To be revisited during Phase 8 execution planning if appropriate, or filed as a separate follow-up.Original Issue (Below — for audit; superseded items struck through)
Finding
CSAPIQueryBuilder.buildQueryString()only appends thelimitquery parameter when explicitly provided by the caller — when omitted, the server applies its own default page size, which varies across implementations (SensorHub: 100, Go CSAPI: 10). Consumers calling any list method without{ limit: N }silently receive truncated results on servers with low defaults.Review Source: Live integration testing against a Go CSAPI server (PostGIS-backed, OGC-compliant) from the ogc-csapi-explorer project and OSHConnect-Python publisher fleet migration.
Severity:
P2-Important→ P3-Suggestion (see status update)Category:
API Design→ DocumentationOwnership: Ours
Problem Statement
The OGC API — Connected Systems specification (OGC 23-001 §7.6) states that
limitis an optional query parameter with a server-defined default. The spec does not mandate a minimum default — so any conformant server can return as few as 1 item per page if nolimitis provided.Our
buildQueryString()method respects this literally: if the caller doesn't passlimit, no?limit=appears in the URL, and the server picks its own default.This is technically correct but creates a practical interoperability trap — the library silently returns different result counts depending on which server it talks to, with no indication to the consumer that results were truncated.Affected code —
buildQueryString()(line ~393):Scenario:
Impact:
Any consumer that calls a list method without an explicitlimitwill silently receive truncated results when connected to a server whose default page size is smaller than the total resource count. This was the root cause of thefind_by_uid()failures in the OSHConnect-Python publisher fleet — systems, datastreams, and deployments were not found because the Go server's defaultlimit=10returned only the first page. The workaround was to hardcode&limit=1000in the Python bootstrap helpers.All 39 public list methods that accept
QueryOptionsare affected, including:getSystems(),getDataStreams(),getObservations()getDeployments(),getProcedures(),getProperties()getControlStreams(),getCommands()get*History(),get*Subsystems(),get*Observations(), etc.Ownership Verification
All affected code is in our diff. The
buildQueryString()method,CSAPIQueryBuilderclass,QueryOptionsinterface, andvalidateLimit()helper were all authored as part of our CSAPI contribution. The upstreamcamptocamp/ogc-clienthas zero CSAPI code.Conclusion: This code is ours.
Files to Modify — Original (Superseded)src/ogc-api/csapi/url_builder.tsModifyAdd configurable defaultlimitwith fallback constantsrc/ogc-api/csapi/model.tsModifyAdd optionaldefaultLimitto builder config orQueryOptionsJSDocsrc/ogc-api/csapi/url_builder.spec.tsModifyTest that default limit is applied whenlimitis omittedProposed Solutions — Original (Superseded)Option A: Client-side default constant (Recommended)Add aDEFAULT_LIMITconstant tourl_builder.tsand apply it inbuildQueryString()when no explicitlimitis provided:Pros: Minimal diff; consistent behavior across all servers; matches SensorHub's default (100); no API surface change — callers can still override with{ limit: N }Cons: Adds a parameter the server may not expect (thoughlimitis always a valid OGC parameter); opinionated defaultEffort: Small | Risk: LowOption B: Configurable default via constructor optionAdd an optionaldefaultLimitproperty to theCSAPIQueryBuilderconstructor config:Pros: Per-instance configurable; consumers can opt out by passingundefined; works well when the same builder talks to different serversCons: Larger diff; requires factory function update; adds constructor complexityEffort: Medium | Risk: LowScope — Original (Superseded)Acceptance Criteria — Original (Superseded)Dependencies
Blocked by: Nothing
Blocks: Nothing
Related: #166 (Part 2 parsers
@linkfallback — same Go server interop effort); #110 (@linkresolution utilities — higher-level pagination concern)Operational Constraints
Key constraints:
Ownership-Specific Constraints
If Ours:
phase-7)clean-prif the PR is still openReferences
src/ogc-api/csapi/url_builder.tsL376-410buildQueryString()— code referenced (no behavior change in revised scope)src/ogc-api/csapi/model.tsL140-175QueryOptionsinterface with optionallimit— JSDoc to be expandedsrc/ogc-api/csapi/helpers.tsL214-220validateLimit()— existing validation (not modified)limitis optional, server picks default — supports the corrected diagnosissrc/ogc-api/endpoint.tsL512 (upstream)getCollectionItems(limit = 10)— calibration for the open questionsrc/ogc-api/csapi/integration/observation.spec.tsL253-267next-link-walking pattern in our own testsbootstrap_helpers.py&limit=1000workaround — consumer-side defect, not ours?uid=— related but separate filtering gap