Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,25 +110,28 @@ There are two halves, and they are usually two different people.

### The browser half

No bundler, no framework, no Kubernetes client. The managed connector uses fetch, and the store is plain ESM:
No bundler, no framework, no Kubernetes client. The connector uses fetch and hands each resource
event to a callback; the store is plain ESM:

```ts
import { LiveResourceStore, connectManagedResourceStream, resourceStreamURL } from "@configbutler/krm-stream";
import { LiveResourceStore, applyStreamEvent, connectResourceStream, resourceStreamURL } from "@configbutler/krm-stream";

const store = new LiveResourceStore();

const connection = connectManagedResourceStream(
const connection = connectResourceStream(
resourceStreamURL("/resource-stream/v1", {
target: "production",
version: "v1",
resource: "configmaps",
namespace: "app",
}),
store,
{
onChange: (change) => render(change.uid), // what moved, and which resource it moved on
(event) => {
const change = applyStreamEvent(store, event);
render(change.uid); // what moved, and which resource it moved on
},
);
connection.subscribe((state) => renderConnection(state.status)); // connecting, syncing, live, retrying…
connection.closed.catch(reportApplicationError); // the callback threw, and the stream stopped

// The user edits. The server keeps changing underneath them. Neither wins by accident.
store.setValue(uid, ["spec", "replicas"], 3);
Expand Down
18 changes: 10 additions & 8 deletions docs/adopting.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ Use the `Authorizer` to pin a user to a namespace or target before any watch ope
```ts
import {
LiveResourceStore,
connectManagedResourceStream,
applyStreamEvent,
connectResourceStream,
resourceStreamURL,
} from "@configbutler/krm-stream";

Expand All @@ -110,14 +111,15 @@ const url = resourceStreamURL("/resource-stream/v1", {
projection: "krm-full/v1",
});

const connection = connectManagedResourceStream(url, store, {
onStateChange: state => renderConnection(state.status), // gaps recover with a fresh snapshot
});
const connection = connectResourceStream(url, event => applyStreamEvent(store, event));
renderConnection(connection.state.status);
connection.subscribe(state => renderConnection(state.status)); // gaps recover with a fresh snapshot
connection.closed.catch(reportApplicationError); // the callback threw, and the stream stopped
store.subscribe(() => render(store));
```

Fetch sends the same-origin session cookie, and the managed connection recovers on its own after
network failures and sequence gaps.
Fetch sends the same-origin session cookie, and the connection recovers on its own after network
failures and sequence gaps.

Saving is the host's: see [saving](saving.md). A quiet stream can still hold an older write version;
see [why a quiet stream can reject a save](saving.md#why-a-quiet-stream-can-still-reject-a-save).
Expand Down Expand Up @@ -207,9 +209,9 @@ refuses redirects so a user's token is not carried along.

### Bearer-token clients

`connectManagedResourceStream(url, store, { headers: { Authorization: ... } })` suits a non-browser
`connectResourceStream(url, consume, { headers: { Authorization: ... } })` suits a non-browser
client or an intentionally token-bearing browser application; the same-origin cookie route is the
safer browser default. The connectors delegate transport to fetch and enforce no credential policy,
safer browser default. The connector delegates transport to fetch and enforces no credential policy,
so the host must require HTTPS for the resolved destination, including redirects, before supplying
credentials.

Expand Down
6 changes: 3 additions & 3 deletions docs/auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ is in [adopting](adopting.md).
## Browser sessions

Use a same-origin session cookie. The host handles OIDC with its identity provider, keeps the tokens
server-side and issues a secure session cookie. The managed fetch connector sends that cookie; native
`EventSource` can use the same route.
server-side and issues a secure session cookie. `connectResourceStream` sends that cookie over fetch;
native `EventSource` can use the same route.

```mermaid
sequenceDiagram
Expand All @@ -38,7 +38,7 @@ sequenceDiagram
S-->>B: Projected SSE stream or terminal error
```

A token-bearing client uses `connectManagedResourceStream` with explicit headers, because native
A token-bearing client uses `connectResourceStream` with explicit headers, because native
`EventSource` cannot send an `Authorization` header. The host must then enforce trusted HTTPS
endpoints and redirect handling; see [adoption](adopting.md#3-connect-the-browser).

Expand Down
7 changes: 5 additions & 2 deletions docs/client-state-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,16 @@ new resource with a new draft.

## Stream lifecycle

Use `applyStreamEvent` or the transport helpers instead of translating protocol events in UI code.
Pass each state event from `connectResourceStream` to `applyStreamEvent` instead of translating
protocol events in UI code. The connector checks the sequence and handles `error` events itself, so
the store only sees state events.

- `reset` starts a snapshot and marks existing resources unseen.
- `added` and `modified` both replace the server object and reconcile the draft.
- `deleted` removes the resource and its draft.
- `synced` prunes resources that were not seen during the completed snapshot.
- `error` is terminal only when the event says it is terminal. `RESYNC_REQUIRED` starts a new
- `error` goes to the connector's `onError`, not to the store. It is terminal only when the event
says it is terminal. `RESYNC_REQUIRED` starts a new
snapshot on the same connection. After any other non-terminal error, such as
`UPSTREAM_UNAVAILABLE`, the gateway closes the connection and the client reconnects, waiting at
least `retryAfterMs`.
Expand Down
21 changes: 19 additions & 2 deletions docs/proposals/0006-stream-and-save-implementation-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@ observations and tested shared-host composition. That work can support the autho
and continuation measurements below, but adds no dependency or acceptance gate to this plan.
Hosts may demonstrate the existing requirements with their own bounded sinks and instrumentation.

[Proposal 0009](0009-stream-and-editor-separation.md) separates browser stream consumption from the
optional editor layer. It owns that API and package-boundary work; this plan retains the existing
behavioral follow-ups and acceptance criteria. Priorities 2–3 below form the editor track, and
priority 4 forms the stream track. Deliver them independently. Complete the browser separation
before expanding the editor's public API; it does not block gateway fixes, real-API evidence or
upstream continuation.

[Proposal 0010](0010-gateway-api-cleanup.md) owns separate Go API cleanup. Use its final serving and
configuration names once implemented; it adds no acceptance gate to the behavioral work here.

## Baseline and order

Use the [adoption guide](../adopting.md), [saving guide](../saving.md) and
Expand All @@ -36,6 +46,12 @@ Keep the existing [user-facing outcomes](../saving.md#what-the-person-editing-se
[receipt contract](../saving.md#answer-204-or-a-receipt-and-let-the-watch-echo-it) in the saving guide.
The remaining work is executable guidance, using existing store and Vue example tests.

When implementing after proposal 0009, use its event-only store input, consolidated connector and
guarded response contract. Successful creates/deletes remain host-owned pending confirmations until
their echo or a completed snapshot confirms state; do not restore unguarded adoption or optimistic
store deletion in a recipe. Keep server acceptance separate from confirmation to avoid duplicate
submissions when an echo is delayed or arrives before the response.

### Deletion recovery copy

Demonstrate a host subscription that snapshots detached `store.draft(uid)` on each notification while
Expand All @@ -51,8 +67,9 @@ recovery copy is not another reconciler. Link the tested recipe from the saving
### Explicit keep-local resolution

Demonstrate capturing the chosen local value (including absence), resolving that path with
`takeTheirs`, then synchronously reapplying it through `setValue` or `removeKey`. Verify the recipe
against current behavior before publishing executable guidance.
`revert`, then synchronously reapplying it through `setValue` or `removeKey`. Proposal 0009 removes
the equivalent `takeTheirs` alias. Verify the recipe against current behavior before publishing
executable guidance.

**Acceptance:** cover nested paths, deletion, whole-array replacement and policy/redaction refusal;
preserve unrelated edits and conflicts. Capture a fresh save intent after review. Add a small helper
Expand Down
Loading
Loading