@nytka/core
v0.1.0
Published
Project plumbing shared by nytka connectors: find the project root, load its .env, write a payload, register a dataset with provenance.
Readme
@nytka/core
Project plumbing shared by nytka connectors: find the project root, load its one .env,
write a payload, register a dataset in datasets/index.json with provenance.
Zero runtime dependencies, and there will never be one. Every connector installs this, so a dependency here is a dependency in every project that collects anything.
What this is not
Not a plugin contract. There is no base class, no plugin registry, no connector abstraction. Two connectors proved they share file-and-registry plumbing; they have not yet proved what a connector is. That waits for a third connector on a third auth mechanism.
Install
npm install @nytka/coreYou normally do not: @nytka/plugin-gsc and @nytka/plugin-ga4 depend on it and re-export
the parts a caller needs. Install it directly only when writing a connector.
API
import {
findProjectRoot, loadEnv, writePayload, registerDataset,
fmtJson, isoDate, monthKey, monthPeriod,
} from '@nytka/core'| | |
|---|---|
| findProjectRoot(start?) | Walks up at most 12 levels for project.yaml. Returns the directory or null. Existence check only — no YAML parsing, no dependency. |
| loadEnv(root) | Loads <root>/.env via process.loadEnvFile. Returns the path or null. One .env per project; plugins read process.env and never learn where a value came from. |
| writePayload(root, id, payload) | Writes datasets/payloads/<id>.json, creating the directory and its own .gitignore. Returns the project-relative path. |
| registerDataset(root, entry) | Adds or replaces the entry with entry.id in datasets/index.json. Returns the path written. |
| fmtJson(value, indent?, width?) | JSON with anything that fits on one line kept on one line. |
| isoDate(date?) | The local calendar date as YYYY-MM-DD. |
| monthKey(date) | 2026-07-25 → 2026-07. Throws on anything that is not a YYYY-MM-DD calendar date. |
| monthPeriod(from, to) | 2025-04-01, 2026-07-25 → 2025-04-to-2026-07. The reporting-period fragment of a dataset id. |
Two things here are load-bearing
fmtJson's width of 150
It is calibrated against real registries, not principled, and it is shared state: every
connector writes the same datasets/index.json. JSON.stringify(x, null, 2) explodes every
short array onto its own set of lines, so appending one entry reformats every entry already
there — a several-hundred-line diff for a one-line change, which is a diff nobody reviews.
Changing the width reformats every entry every other connector ever wrote.
test/fmtjson.test.mjs holds it to a byte-identical round trip on a registry that already
went through it.
isoDate is local, not UTC
new Date().toISOString().slice(0, 10) converts to UTC first, so anywhere west of Greenwich
an evening run stamps tomorrow — a collection at 20:06 EDT on 2026-07-28 records
collectedAt: 2026-07-29, a date in the future against every other date in the project,
which are all local calendar dates.
Dataset id convention
Ids name the reporting period at month granularity:
<source>-<operation>-<property-slug>-<YYYY-MM>-to-<YYYY-MM>The exact window a collection actually fetched stays in the entry's dateRange. That split
is deliberate. Connectors fetch up to now - lag, so an id built from the exact window moves
every day and registerDataset — which matches on id — appends instead of replacing. Running
a monthly collection daily for a month would leave thirty near-identical entries describing
one series.
At month granularity the same month collected twice replaces in place, a new month is a new entry, and no provenance is lost.
Tests
npm testnode --test. No dependency, no network, no credential.
License
MIT
