Tutorial: Dependencies

Dependencies

Dependencies

What this service talks to, and the modules that do the talking. Per-process usage is noted in each process doc; this page is the single reference for the dependency itself.


Service modules

One row per module in src/services/.

Module Wraps Used by
asyncRequestApi.js async-request-backend check, submit
datasette.js Datasette SQL over HTTP all
performanceDbApi.js SQL builders over datasette review-data-quality
platformApi.js planning.data.gov.uk entity API review-data-quality
boundaryService.js LPA boundary GeoJSON check results map, dashboards
osMapService.js OS Maps access token map components
jiraService.js Jira Service Desk submit
mailClient.js GOV.UK Notify submit
columnMappingDecider.js datasette issue-type query check

asyncRequestApi

The check backend. postUrlRequest(formData) and postFileRequest(formData) create a request and return its id; getRequestData(resultId) retrieves it, wrapped in a RequestData model. A 404 from getRequestData is distinguished from other failures so callers can redirect rather than error.

Note that getRequestData uses a hardcoded 15s axios timeout, not config.asyncRequestApi.requestTimeout — that setting is declared and schema-validated in config/util.js but read nowhere, so changing it has no effect. Its opts parameter is also unused.

Request status is PENDING/PROCESSING until it reaches COMPLETE or FAILED — both count as finished, which is why isComplete() returns true for a failed request and callers must check isFailed() as well.

jiraService

Creates a Service Desk customer request (createCustomerRequest) under config.jira.requestTypeId, then attaches the answers CSV in two steps (attachTemporaryFile, then attach to the issue) via attachFileToIssue. Non-production environments use the same Jira instance and prefix ticket titles with [TEST].

Models

models/requestData.js and models/responseDetails.js wrap async API payloads.

RequestData is the interface controllers should use rather than the raw payload — it tolerates missing fields and logs when something is absent, instead of throwing halfway through a render.

Method Returns
isComplete() status is COMPLETE or FAILED — i.e. processing has stopped
isFailed() status is FAILED
hasErrors() any task in the task log has responsibility: 'external' — the user's problem, not ours
getError() the error object from the response
getParams() the original request params: dataset, organisationName (org code), url or original_filename, column_mapping
getType() check_url or the file equivalent — how submit knows an endpoint exists
getColumnFieldLog() per-field column mapping, used by the column-mapping step
fetchResponseDetails(offset, limit) paginated ResponseDetails for the results table

External dependencies

Dependency Used for Health check
async-request-backend running checks request-api
Datasette all reference and performance data datasette
Planning Data platform API published entities and tasks
AWS S3 uploaded files s3-bucket
Redis session store redis (optional)
Jira Service Desk provide requests
GOV.UK Notify confirmation emails
OS Maps basemap tiles
Sentry error and metric reporting
Smartlook / Google Analytics usage analytics

routes/health.js checks the four marked above and reports status, environment, version and maintenance.

Hard — the service is unusable without them: async-request-backend (no checks can run) and datasette (no dataset, organisation or issue data, and the app will not boot because the startup name queries fail).

Soft — degraded but working: Redis is explicitly optional and setupSession falls back to an in-memory store, which means sessions are lost on restart and do not survive across instances; S3 only affects file uploads; Jira and Notify only affect providing; OS Maps only affects map rendering; Sentry and the analytics tools affect nothing user-facing.

Datasette

Client

services/datasette.js exposes runQuery(query, database), which requests ${config.datasetteUrl}/${database}.json?sql=….

It returns the raw datasette response plus formattedData — the rows converted from positional arrays into objects keyed by column name. Read formattedData; the raw rows/columns are rarely what you want.

On failure it increments the datasette_query_errors Sentry counter, logs the query and URL, and rethrows. Chains built with fetchOne/fetchMany turn that into an error page; direct callers must handle it themselves.

Queries go in the URL, so very long generated SQL can hit URL length limits — watch for this when building IN clauses from large lists.

Databases

digital-land is the default and holds reference data (organisations, datasets, specification, sources, endpoints). Entity and issue data lives in a database per dataset, passed as the second argument to runQuery. Any query can be pasted into datasette.planning.data.gov.uk to check it by hand.

Query modules

utils/datasetteQueries/ — standalone queries:

Module Returns
fetchDatasetCollections.js dataset → collection mapping
fetchDatasetsFromProvisions.js datasets an organisation is expected to provide
fetchLocalAuthorities.js LPA list
getDatasetSlugNameMapping.js slug → readable name (loaded at startup)
getOrganisationNameMapping.js org code → name (loaded at startup)
endpointAlreadyCollected.js whether an endpoint URL is already collected

Query builders for the dashboards live in performanceDbApi.js — see review-data-quality.

Caching

The dataset-slug and organisation-name mappings are loaded once at startup and held in memory — they change rarely, and a restart is the way to pick up changes.

utils/redisLoader.js caches provision reasons and dataset names in Redis with a 6-hour TTL (CACHE_TTL), and also holds the short-lived endpoint submission reservations (reserveSubmittedEndpoint, settleSubmittedEndpoint) that stop the same endpoint being submitted twice concurrently.

Nothing else is cached — dashboard queries hit datasette on every request.

Digital Land repositories

Repository How this service uses it
specification dataset fields and rules, read via datasette (fetchSpecification, fetchDatasetFields); relationship diagrams pulled at build time by scripts/fetch-specification-diagrams.js using config.specificationDiagrams.baseUrl
config collection and pipeline configuration; surfaced through datasette. Endpoints provided through this service end up here, added by the data team
async-request-backend runs the actual check
config-manager the Manage service — where provided endpoints are configured

digital-land-python

Not a direct dependency of this service. This is a Node application; it has no Python runtime. digital-land-python runs inside the async request backend and the collection pipelines. Check results therefore reflect its behaviour without this codebase importing it.

When a check result looks wrong, the cause is usually there rather than here: issue types and their severities, file conversion, field mapping and expectations are all produced by the pipeline code. This service only renders what comes back.

npm dependencies worth knowing

The ones that shape how the code is written, rather than just being present:

Package Shapes
hmpo-form-wizard the whole structure of check and submit — steps, fields, session model
hmpo-config YAML config loading and merging
nunjucks + govuk-frontend all rendering; templates in src/views
valibot schemas in routes/schemas.js and deep-link param validation
multer file uploads
maplibre-gl + wellknown maps and WKT geometry rendering
winston structured logging via utils/logger.js and the types in utils/logging.js
@sentry/node error reporting and the Sentry.metrics.count counters used for triage