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
-**Lint:** oxlint (explicit config at `.oxlintrc.json`)
11
11
-**Format:** oxfmt
12
12
-**Package lint:** publint + attw (Are The Types Wrong)
13
13
-**Publish:** OIDC Trusted Publishing to public npm registry (no stored tokens)
@@ -54,10 +54,63 @@ Shared frontend service packages monorepo under the `@script-development` npm sc
54
54
55
55
**Build before typecheck.** Cross-package type resolution requires built `.d.mts` files. The CI pipeline enforces this order.
56
56
57
+
## Lint Rules
58
+
59
+
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:
60
+
61
+
-**Plugins:**`typescript`, `unicorn`, `oxc` — the three plugins enabled by oxlint's own defaults.
62
+
-**Categories:**`correctness: "error"` — all 107 Correctness rules fail CI (was `warn`, so violations were silently tolerated pre-config).
63
+
-**`perf`, `suspicious`, `pedantic`, `style`, `restriction`, `nursery`:** unset — library posture is Correctness-only, opt-in per-rule for anything else.
64
+
65
+
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.
66
+
57
67
## Adding a Package
58
68
59
69
1. Create `packages/{name}/` with `package.json`, `tsconfig.json`, `tsdown.config.ts`, `vitest.config.ts`
60
70
2. Name it `@script-development/fs-{name}`
61
71
3. Use `defineProject` from `vitest/config` in the vitest config
62
72
4. Add 100% coverage threshold and 90% mutation threshold
63
73
5. Bump version in the new package's `package.json` (manual — no changeset `.md` files)
74
+
75
+
## War Room ADR Projections
76
+
77
+
Distilled operational rules from cross-project Architecture Decision Records. Canonical source: [adrs.script.nl](https://adrs.script.nl). This section is maintained by the War Room — do not edit directly.
78
+
Last synced: 2026-04-17
79
+
80
+
### Applicable
81
+
82
+
#### ADR-0013: Adapter-Store Pattern
83
+
84
+
- Published here as `fs-adapter-store`. This territory is the canonical home of the pattern.
85
+
- Preserve the reactive adapter-store contract: `createAdapterStoreModule()` factory returning a module with `resourceAdapter` for CRUD plus typed `Adapted<T>` / `NewAdapted<T>` records.
86
+
- 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).
87
+
88
+
#### ADR-0015: ADR Governance
89
+
90
+
- War Room ADRs are canonical at `adrs.script.nl`. Projections (this section) are distilled into territory CLAUDE.md by the War Room.
91
+
- Do not amend projections in this file directly. Propose amendments through the war room; the update propagates here.
92
+
- fs-packages is a full territory under the war room (not exempt like BIO).
93
+
94
+
#### ADR-0017: Page Integration Tests
95
+
96
+
- 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.
97
+
- Do not introduce breaking changes to fs-http's public API without coordinating with consumer territories' mock-server infrastructure.
98
+
99
+
### Not Applicable (Library Territory Rationale)
100
+
101
+
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-0014** Domain-Driven Frontend Structure — App-level vertical slices by business domain; fs-packages is horizontal library infrastructure, not an app. N/A.
-**ADR-0019** Explicit Model Hydration — Eloquent model hydration; N/A.
111
+
112
+
Kendo-only or territory-scoped ADRs (0003, 0004, 0006, 0008, 0018) do not apply cross-territory.
113
+
114
+
### Internal / War-Room-Only
115
+
116
+
ADR-0005 (Spy System), ADR-0007 (Soldiers + Briefings), ADR-0010 (Squad System) govern war room operations, not territory code. No projection required.
Copy file name to clipboardExpand all lines: docs/packages/adapter-store.md
+69-14Lines changed: 69 additions & 14 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -181,6 +181,59 @@ try {
181
181
182
182
The store automatically persists state to the provided storage service. When the page reloads, stored data is available immediately while `retrieveAll()` fetches fresh data from the API. This provides a fast initial render without loading spinners.
183
183
184
+
## Syncing External Updates
185
+
186
+
Some resources are updated outside of the store's own CRUD calls — by another user over a WebSocket, by a background job, by an in-process event emitter. The `broadcast` config slot is the single, narrow bridge for feeding those updates into the store without going through HTTP.
The store calls `subscribe` exactly once at construction and wires the handlers straight into its internal mutation path. `onUpdate(item)` replaces or inserts; `onDelete(id)` removes. Both update reactive state, refresh adapted views, and persist to storage — identical to what `update()` / `delete()` do after a successful HTTP call.
213
+
214
+
::: tip Why isn't there a public `setById` / `applyUpdate` method?
215
+
By design. Exposing a raw mutation method would let any caller bypass HTTP, which is almost always a bug (you'd end up with stale server state). The `broadcast` contract forces the bridge to be declared explicitly at store construction, scoped to one event source per store.
216
+
:::
217
+
218
+
### Lifecycle
219
+
220
+
The `subscribe` call happens once, when the store is created. The unsubscribe return is retained internally and never exposed. In practice stores live for the app's lifetime, so teardown isn't needed — but if your event source has its own lifecycle (e.g., a channel you join and leave), manage that _outside_ the store. The store only cares about incoming events, not which channel they came from.
221
+
222
+
A common pattern is a small in-process emitter as a middleman: your transport layer (WebSocket, SSE, channel service, whatever) joins and leaves connections as views mount/unmount, and forwards incoming payloads onto an emitter that the store subscribes to. The store stays agnostic of transport and lifecycle.
223
+
224
+
### The Contract
225
+
226
+
```typescript
227
+
typeAdapterStoreBroadcast<T> = {
228
+
subscribe: (handlers: {
229
+
onUpdate: (item:T) =>void;
230
+
onDelete: (id:number) =>void;
231
+
}) => () =>void; // unsubscribe
232
+
};
233
+
```
234
+
235
+
That's it. Any event source that can emit "updated" and "deleted" events for your resource type can implement this.
236
+
184
237
## Custom New Types
185
238
186
239
By default, `generateNew()` creates an object with all fields except `id`. You can customize this with a third type parameter:
0 commit comments