mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 21:46:49 +00:00
7a7903a799
The offline core only works when the views go through it: component, feature hook, store, repo, then api or Dexie, with writes through the mutation queue. A view that imports src/api/ skips the cache, the optimistic write and the idempotent replay. client/CLAUDE.md said so and noted that no ratchet held the line, and the number of view files importing api/client grew from 161 to 190 in two months. lint:offline lists the 213 files under components/, mobile/, pages/ and hooks/ that import anything from src/api/ for more than types (api/client, the websocket, the per-domain modules) in scripts/offline-baseline.json. No other view file may, and a listed file that stops leaves the list through --update, so the next file at that path does not inherit the exemption. Type-only imports do not count: they carry no request. client/CLAUDE.md describes the rule next to the data-flow section, including that an online-only domain puts its calls in a store or repo.
78 lines
2.8 KiB
JavaScript
78 lines
2.8 KiB
JavaScript
/*
|
|
* The comparison behind lint:offline (scripts/offline-lint.mjs).
|
|
*/
|
|
import { join } from 'node:path'
|
|
import { fileList, lowerList, readBaseline, writeBaseline } from './ratchet.mjs'
|
|
import { sourceGraph } from './imports.mjs'
|
|
|
|
/** The layers that render or drive a view, and so must not hold a network call. */
|
|
export const VIEW_LAYERS = ['components', 'mobile', 'pages', 'hooks']
|
|
|
|
/**
|
|
* The view files that import from src/api/ for anything but types, each with
|
|
* the imports that do it. A type-only import is erased from the bundle and
|
|
* carries no request, so it does not count.
|
|
*/
|
|
export function bypasses(root) {
|
|
const found = {}
|
|
for (const file of sourceGraph(root)) {
|
|
if (!VIEW_LAYERS.includes(file.layer)) continue
|
|
const direct = file.imports.filter((imp) => imp.layer === 'api' && !imp.typeOnly)
|
|
if (direct.length) found[file.key] = direct.map((imp) => `${imp.line}: '${imp.specifier}'`)
|
|
}
|
|
return found
|
|
}
|
|
|
|
/**
|
|
* Runs the check against the list at baselinePath. A view file outside the
|
|
* list may not import src/api/. A listed file that no longer does fails the
|
|
* check as well until --update takes it out, so the list only shrinks and
|
|
* the next file at that path does not inherit the exemption. Returns the exit
|
|
* code.
|
|
*/
|
|
export function check({
|
|
root,
|
|
baselinePath = join(root, 'scripts/offline-baseline.json'),
|
|
update = false,
|
|
list = false,
|
|
log = console.log,
|
|
error = console.error,
|
|
}) {
|
|
const found = bypasses(root)
|
|
const offenders = Object.keys(found)
|
|
let baseline = readBaseline(baselinePath, fileList)
|
|
|
|
if (update) {
|
|
baseline = lowerList(baseline, offenders)
|
|
writeBaseline(baselinePath, baseline)
|
|
}
|
|
|
|
const allowed = new Set(baseline)
|
|
const fresh = offenders.filter((key) => !allowed.has(key))
|
|
const stale = baseline.filter((key) => !found[key])
|
|
|
|
if (list) {
|
|
for (const [key, direct] of Object.entries(found)) {
|
|
log(`${allowed.has(key) ? 'listed' : 'new '} ${key}`)
|
|
for (const line of direct) log(` ${line}`)
|
|
}
|
|
}
|
|
for (const key of fresh) {
|
|
error(`FAIL ${key} imports src/api/ directly:`)
|
|
for (const line of found[key]) error(` ${line}`)
|
|
}
|
|
if (fresh.length) {
|
|
error(
|
|
'A view reads through a repo (src/repo/) or a store and writes through the mutation queue, ' +
|
|
'so it keeps working offline. A domain that is online-only on purpose still gets its calls ' +
|
|
'in a store or a repo, not in the view. See client/CLAUDE.md, "Data flow".',
|
|
)
|
|
}
|
|
for (const key of stale) {
|
|
error(`FAIL ${key} is listed in scripts/offline-baseline.json but no longer imports src/api/ (or is gone).`)
|
|
}
|
|
if (stale.length) error('Run npm run lint:offline -- --update to take it off the list.')
|
|
log(`offline: ${offenders.length} view file(s) import src/api/ directly, ${baseline.length} listed in the baseline`)
|
|
return fresh.length || stale.length ? 1 : 0
|
|
}
|