Skip to content

Latest commit

 

History

History
161 lines (114 loc) · 13.8 KB

File metadata and controls

161 lines (114 loc) · 13.8 KB

fs-packages — The Armory

Shared frontend service packages monorepo under the @script-development npm scope.

Stack

  • Language: TypeScript ^6.0 (strict mode, verbatimModuleSyntax)
  • Build: tsdown (Rolldown/oxc) — dual ESM + CJS output
  • Test: vitest 4 (100% coverage threshold) + Stryker (90% mutation threshold)
  • Lint: oxlint (explicit config at .oxlintrc.json)
  • Format: oxfmt
  • Package lint: publint + attw (Are The Types Wrong) — lint:pkg enforces fail-on-any-advisory via scripts/lint-pkg.mjs (suggestions, warnings, and errors all treat as fatal — publint CLI default and --strict both exit 0 on suggestions). Motivated by enforcement queue #33 + the PR #35 git+ prefix regression that silently drifted across 10 packages because the unenforced gate only printed the suggestion. The same wrapper also asserts engines.node presence across the root manifest + all workspace packages — closes enforcement queue #31 (drift-prevention gate, deployed 2026-05-12; declarations themselves landed 2026-04-22 via commit 0605d99). Presence-only check; the value (>=24.0.0 today) is not validated — value alignment is a separate doctrine question tracked alongside the CI node-version.
  • Publish: OIDC Trusted Publishing to public npm registry (no stored tokens)
  • CI: 8-gate pipeline: audit → format → lint → build → typecheck → lint:pkg → coverage → mutation

Doctrine #8 — HTTP Timeout Surface (fs-http)

fs-http is the war-room reference implementation of Doctrine #8 (Architectural Principle #8, library-author extension — see war-room CLAUDE.md ## Architectural Principles §8, 2026-04-22):

Library-author extension (2026-04-22) — Shared HTTP factory packages (e.g., @script-development/fs-http) must expose a compliant timeout surface: a default, a required option, or a documented contract plus consumer-level enforcement. Inheriting framework defaults at the library layer silently propagates the violation to every consumer territory.

fs-http exposes a 5-axis timeout surface:

  • Default: 30000 ms applied if no override is provided.
  • Service-wide option: createHttpService(baseURL, { timeout: number }).
  • Per-request override: standard axios timeout config on individual calls.
  • Opt-out: timeout: 0 disables the timeout (use sparingly).
  • Constant export: DEFAULT_TIMEOUT_MS is barrel-exported for consumers that need to reference the default explicitly.

Consumer territories must apply per-call timeouts at instantiation OR rely on the 30000 ms default. See docs/packages/http.md#timeout for usage.

Packages (11)

Package Vue Description
fs-http No HTTP service factory with middleware architecture
fs-storage No localStorage service factory with prefix namespacing
fs-helpers No Tree-shakeable utilities: deep copy, type guards, case conversion
fs-theme Yes Reactive dark/light mode with storage persistence
fs-loading Yes Loading state service with HTTP middleware
fs-adapter-store Yes Reactive adapter-store pattern with CRUD resource adapters
fs-cached-adapter-store Yes Hash-bumping cache wrapper around fs-adapter-store; middleware-driven invalidation with prime() bootstrap; no retrieveAll/retrieveById on the public surface
fs-toast Yes Component-agnostic toast queue (FIFO)
fs-dialog Yes Component-agnostic dialog stack (LIFO) with error middleware
fs-translation Yes Type-safe reactive i18n with dot-notation keys
fs-router Yes Type-safe router service factory with CRUD navigation, middleware pipeline, and custom components for Vue Router

Conventions

  • Factory pattern: All packages export createXxxService() factory functions returning plain service objects. No classes, no singletons.
  • Single entry point: Each package has src/index.ts as the sole barrel export. Named exports only, no defaults.
  • Peer dependencies: Vue-dependent packages declare vue as a peer dep. Inter-package dependencies are peers too.
  • Loose coupling: Prefer structural typing (duck types) over direct package imports where possible. fs-theme's ThemeStorageContract is the exemplar.
  • Test environment: Browser-dependent tests use // @vitest-environment happy-dom file-level comments.
  • Identical build config: All packages share the same tsdown.config.ts structure.
  • No direct axios imports in dependent packages. Route AxiosResponse / AxiosRequestConfig / sibling types through fs-http's re-exports (e.g. Parameters<ResponseMiddlewareFunc>[0] for response types). Direct import type {AxiosResponse} from 'axios' breaks rolldown's d.cts emission on dual-bundle packages — caught during fs-cached-adapter-store scaffold 2026-05-13.
  • Transport-surface discipline. Every fs-http transport method must inherit option-honoring from the axios.create() instance. Adding a new transport path that uses native fetch (or any non-axios transport) requires a deliberate audit against the full HttpServiceOptions matrix — headers, withCredentials, withXSRFToken, smartCredentials, timeout, plus the per-call AxiosRequestConfig override surface. The Library-Config-Honor Surface Audit (Sapper M3 + Surveyor M3, 2026-05-15) is the standing checklist. The pre-1.0 streamRequest function violated this rule on four axes (queue #22 streamRequest portion + queue #64 XSRF + Surveyor M3 F-1 headers + F-2 timeout) and was removed in 0.4.0 with zero realized consumer impact. If a future streaming use case emerges, the right design is either axios's responseType: 'stream' mode via the standard methods (inherits all options for free) or a deliberate createStreamHttpService factory designed against the option-honoring matrix from the start — not a re-add of an axios-bypassing transport.

Internal Dependency Coordination

Two packages share an internal direct-dep on string-ts: fs-helpers (deepCamelKeys, deepSnakeKeys, DeepSnakeKeys type) and fs-translation (replaceAll). Symbols are disjoint, npm dedupes the dep in consumer node_modules when ranges align, and tsdown externalizes string-ts in both bundles — consumers using both packages do not ship duplicate copies.

Discipline: when bumping string-ts in either package, bump it in the other in the same PR. Range drift across the two consumers (e.g. one on ^2.x, the other on ^3.x) breaks consumer dedupe.

Versioning Discipline (Pre-1.0)

While packages remain pre-1.0, npm caret semantics treat every minor bump as breaking (^0.1.0 matches only 0.1.x). Each fs-http minor bump cascades into peer-range widenings on fs-loading, fs-adapter-store, and fs-cached-adapter-store. The cascade is mechanical, not avoidable on npm.

Per-bump checklist:

  1. Grep all packages/*/package.json for the bumped package's name.
  2. For every match in dependencies / devDependencies / peerDependencies, widen the range (e.g. ^0.1.0^0.1.0 || ^0.2.0).
  3. Patch-bump the affected sibling packages — the peer-range widening is observable in published metadata and deserves its own version.
  4. Regenerate package-lock.json and verify every node_modules/@script-development/* resolves to the workspace ("resolved": "packages/*", "link": true). No nested registry copies anywhere in the lock.
  5. CI passing npm ci is necessary but not sufficient — inspect the lock for nested copies after every cross-minor bump.

Cascade peers as of 2026-05-13:

  • An fs-http minor bump cascades to: fs-loading, fs-adapter-store, fs-cached-adapter-store.
  • An fs-adapter-store minor bump cascades to: fs-cached-adapter-store.
  • An fs-storage minor bump cascades to: fs-adapter-store, fs-cached-adapter-store.

This tax disappears once packages reach 1.0. The workspace:* protocol is not an option on npm (npm 11+ rejects it as EUNSUPPORTEDPROTOCOL); it is a pnpm/yarn feature.

Commands

Command Purpose
npm run build Build all packages (tsdown)
npm run typecheck Type-check all packages (requires build first)
npm test Run all tests
npm run test:coverage Run tests with coverage (100% threshold)
npm run test:mutation Run Stryker mutation testing (90% threshold)
npm run lint Lint with oxlint
npm run format:check Check formatting with oxfmt
npm run format Fix formatting with oxfmt
npm run lint:pkg Run publint + attw on all packages
npm audit Check for dependency vulnerabilities

Build before typecheck. Cross-package type resolution requires built .d.mts files. The CI pipeline enforces this order.

Lint Rules

Lint configuration lives at .oxlintrc.json (repo-root, no per-package overrides). The explicit config declares three defaults so rule additions/removals land as a deliberate diff rather than silent upstream drift when oxlint bumps:

  • Plugins: typescript, unicorn, oxc — the three plugins enabled by oxlint's own defaults.
  • Categories: correctness: "error" — all 107 Correctness rules fail CI (was warn, so violations were silently tolerated pre-config).
  • perf, suspicious, pedantic, style, restriction, nursery: unset — library posture is Correctness-only, opt-in per-rule for anything else.

To add a rule, set it in the rules object (e.g. "perf/no-accumulating-spread": "error"). To disable a default, set it to "off". To opt into a whole category, add it to categories (be deliberate — pedantic has false positives, nursery is unstable). See npx oxlint --rules for the full catalog with default-on/off markers.

Adding a Package

  1. Create packages/{name}/ with package.json, tsconfig.json, tsdown.config.ts, vitest.config.ts
  2. Name it @script-development/fs-{name}
  3. Use defineProject from vitest/config in the vitest config
  4. Add 100% coverage threshold and 90% mutation threshold
  5. Bump version in the new package's package.json (manual — no changeset .md files)

War Room ADR Projections

Distilled operational rules from cross-project Architecture Decision Records. Canonical source: adrs.script.nl. This section is maintained by the War Room — do not edit directly. Last synced: 2026-04-17

Applicable

ADR-0013: Adapter-Store Pattern

  • Published here as fs-adapter-store. This territory is the canonical home of the pattern.
  • Preserve the reactive adapter-store contract: createAdapterStoreModule() factory returning a module with resourceAdapter for CRUD plus typed Adapted<T> / NewAdapted<T> records.
  • Changes to the pattern's surface (function signatures, exported types) are breaking for every consumer — treat them as major version decisions and coordinate with consumer territories (kendo, BIO).

ADR-0015: ADR Governance

  • War Room ADRs are canonical at adrs.script.nl. Projections (this section) are distilled into territory CLAUDE.md by the War Room.
  • Do not amend projections in this file directly. Propose amendments through the war room; the update propagates here.
  • fs-packages is a full territory under the war room (not exempt like BIO).

ADR-0017: Page Integration Tests

  • Kendo, BIO, and Entreezuil mock only @script-development/fs-http when running page integration tests. fs-http is the mock target; its public API (createHttpService, middleware hooks, isAxiosError) is the contract consumers depend on.
  • Do not introduce breaking changes to fs-http's public API without coordinating with consumer territories' mock-server infrastructure.

Not Applicable (Library Territory Rationale)

The following cross-project ADRs do not apply to fs-packages because it has no Laravel/PHP backend, no HTTP API surface, no database, and no app-UI:

  • ADR-0001 Audit Logging — Laravel/DB-backed; N/A.
  • ADR-0002 Cascade Deletion & Selective Soft Deletes — Laravel/DB-backed; N/A.
  • ADR-0009 Unified ResourceData Pattern — Laravel JSON response shape; N/A.
  • ADR-0011 Action Class Architecture — PHP final readonly action classes; N/A.
  • ADR-0012 FormRequest → DTO Flow — Laravel request pipeline; N/A.
  • ADR-0014 Domain-Driven Frontend Structure — App-level vertical slices by business domain; fs-packages is horizontal library infrastructure, not an app. N/A.
  • ADR-0016 Config Attribute Injection — PHP #[Config] attribute; N/A.
  • ADR-0019 Explicit Model Hydration — Eloquent model hydration; N/A.

Kendo-only or territory-scoped ADRs (0003, 0004, 0006, 0008, 0018) do not apply cross-territory.

Internal / War-Room-Only

ADR-0005 (Spy System), ADR-0007 (Soldiers + Briefings), ADR-0010 (Squad System) govern war room operations, not territory code. No projection required.