@beignet/core 0.0.53 → 0.0.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/README.md +117 -2
- package/dist/broadcasting/client.d.ts +45 -0
- package/dist/broadcasting/client.d.ts.map +1 -0
- package/dist/broadcasting/client.js +599 -0
- package/dist/broadcasting/client.js.map +1 -0
- package/dist/broadcasting/index.d.ts +45 -0
- package/dist/broadcasting/index.d.ts.map +1 -0
- package/dist/broadcasting/index.js +89 -0
- package/dist/broadcasting/index.js.map +1 -0
- package/dist/broadcasting/server.d.ts +83 -0
- package/dist/broadcasting/server.d.ts.map +1 -0
- package/dist/broadcasting/server.js +213 -0
- package/dist/broadcasting/server.js.map +1 -0
- package/dist/notifications/index.d.ts +14 -0
- package/dist/notifications/index.d.ts.map +1 -1
- package/dist/notifications/index.js +17 -0
- package/dist/notifications/index.js.map +1 -1
- package/dist/ports/redaction.d.ts +1 -1
- package/dist/ports/redaction.d.ts.map +1 -1
- package/dist/ports/redaction.js +3 -0
- package/dist/ports/redaction.js.map +1 -1
- package/dist/server/server.d.ts +4 -2
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +1 -1
- package/dist/server/server.js.map +1 -1
- package/package.json +13 -1
- package/skills/app-architecture/SKILL.md +46 -0
- package/src/broadcasting/client.ts +856 -0
- package/src/broadcasting/index.ts +175 -0
- package/src/broadcasting/server.ts +352 -0
- package/src/notifications/index.ts +32 -0
- package/src/ports/redaction.ts +3 -0
- package/src/server/server.ts +8 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# @beignet/core
|
|
2
2
|
|
|
3
|
+
## 0.0.55
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- a3085a6: Allow broadcast connections to configure lifetimes up to one hour while keeping
|
|
8
|
+
the 60-second default. Browser clients honor the server's advertised lifetime
|
|
9
|
+
with bounded watchdog grace, preserve heartbeat timeouts, and reconcile on
|
|
10
|
+
renewal. Align CLI diagnostics and hosting guidance with configurable lifetimes.
|
|
11
|
+
- a3085a6: Coordinate broadcast refreshes with matching pending mutations or custom write
|
|
12
|
+
gates. Add connection admission with automatic resource cleanup, and expose
|
|
13
|
+
connection/reconciliation reasons. Keep the 60-second default, require valid
|
|
14
|
+
lifetime metadata, and use explicit connection-wide renewal control frames.
|
|
15
|
+
|
|
16
|
+
## 0.0.54
|
|
17
|
+
|
|
18
|
+
### Patch Changes
|
|
19
|
+
|
|
20
|
+
- b71b34f: Add typed, authorized browser broadcasting with bounded SSE connections, memory
|
|
21
|
+
and Redis providers, reconnect reconciliation, React Query invalidation,
|
|
22
|
+
initiating-client exclusion, notification delivery, and transactional inbox
|
|
23
|
+
scaffolding. Raw routes now preserve context added by route hooks.
|
|
24
|
+
|
|
3
25
|
## 0.0.53
|
|
4
26
|
|
|
5
27
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
This package provides Beignet's framework primitives: contracts, server runtime,
|
|
12
12
|
typed client, use cases, agent capabilities, ports, domain helpers, app errors, config, events,
|
|
13
13
|
idempotency, locks, outbox, mail, notifications, payments, search, webhooks,
|
|
14
|
-
feature flags, error reporting, encryption, schedules, uploads, entitlements, pagination
|
|
14
|
+
feature flags, error reporting, encryption, broadcasting, schedules, uploads, entitlements, pagination
|
|
15
15
|
helpers, testing helpers, and OpenAPI generation.
|
|
16
16
|
|
|
17
17
|
## Installation
|
|
@@ -50,6 +50,9 @@ name the framework area they depend on.
|
|
|
50
50
|
| --- | --- |
|
|
51
51
|
| `@beignet/core/agent-capabilities` | Typed agent capability definitions, registries, validation, and execution |
|
|
52
52
|
| `@beignet/core/application` | Use case builder and test helpers |
|
|
53
|
+
| `@beignet/core/broadcasting` | Browser-safe typed channel definitions and transport validation |
|
|
54
|
+
| `@beignet/core/broadcasting/server` | BroadcastPort, authorization bindings, registry, and authenticated origins |
|
|
55
|
+
| `@beignet/core/broadcasting/client` | Multiplexed streaming browser client with reconnect reconciliation |
|
|
53
56
|
| `@beignet/core/client` | Typed HTTP client |
|
|
54
57
|
| `@beignet/core/client-only` | Static lint marker for modules intended for client-side imports |
|
|
55
58
|
| `@beignet/core/config` | Environment config validation |
|
|
@@ -1176,7 +1179,7 @@ contracts should stay on explicit paths; catch-all contract patterns such as
|
|
|
1176
1179
|
For routes that cannot be contracts at all — third-party callback endpoints
|
|
1177
1180
|
with externally defined request shapes, signature-verified webhooks,
|
|
1178
1181
|
streaming endpoints that own body consumption —
|
|
1179
|
-
`server.rawRoute({ name, method, path, metadata }).handle(fn)` builds a
|
|
1182
|
+
`server.rawRoute({ name, method, path, metadata, hooks }).handle(fn)` builds a
|
|
1180
1183
|
handler that still runs the whole pipeline (hooks, context creation,
|
|
1181
1184
|
instrumentation, framework error mapping) without contract parsing or
|
|
1182
1185
|
validation. The request body stays unconsumed for the handler, `metadata`
|
|
@@ -2743,6 +2746,118 @@ field requiredness, descriptions, and constraints.
|
|
|
2743
2746
|
- [`@beignet/nuqs`](https://beignetjs.com/nuqs) - URL query state integration with nuqs
|
|
2744
2747
|
- [`@beignet/devtools`](https://beignetjs.com/devtools) - Local request, provider, and audit timeline
|
|
2745
2748
|
|
|
2749
|
+
## Broadcasting
|
|
2750
|
+
|
|
2751
|
+
Define browser-safe channels with `defineChannel(name, { params, events })` from
|
|
2752
|
+
`@beignet/core/broadcasting`. Parameters parse to flat string records; event
|
|
2753
|
+
schemas produce stable canonical JSON. `BroadcastValidationError` rejects
|
|
2754
|
+
invalid data and non-idempotent transforms.
|
|
2755
|
+
|
|
2756
|
+
On the server, import `BroadcastPort` and `createBroadcasting<AppContext>()`
|
|
2757
|
+
from `@beignet/core/broadcasting/server`. Its `defineChannelBinding` requires an
|
|
2758
|
+
explicit authorization callback for every channel, and `defineChannelRegistry`
|
|
2759
|
+
rejects duplicate names. Use memory or Redis providers and expose the registry
|
|
2760
|
+
with `createBroadcastRoute` from `@beignet/web` or `@beignet/next`.
|
|
2761
|
+
|
|
2762
|
+
```ts
|
|
2763
|
+
import { defineChannel } from "@beignet/core/broadcasting";
|
|
2764
|
+
import { z } from "zod";
|
|
2765
|
+
|
|
2766
|
+
export const changes = defineChannel("issues.changes", {
|
|
2767
|
+
params: z.object({ workspaceId: z.string() }),
|
|
2768
|
+
events: { changed: z.object({ issueId: z.string() }) },
|
|
2769
|
+
});
|
|
2770
|
+
```
|
|
2771
|
+
|
|
2772
|
+
`broadcast.publish(changes, { params, event: "changed", data })` resolves on
|
|
2773
|
+
provider acceptance. `broadcast.subscribe(changes, { params, onEvent,
|
|
2774
|
+
onDisconnect })` returns `{ ready, unsubscribe }`; await readiness and cleanup.
|
|
2775
|
+
Publish after commit, or record a publication job in the transaction's outbox
|
|
2776
|
+
when the attempt must be retryable. Browser delivery is ephemeral.
|
|
2777
|
+
|
|
2778
|
+
`createBroadcastClient({ url, headers?, credentials?, fetch? })` from
|
|
2779
|
+
`@beignet/core/broadcasting/client` shares one streaming Fetch connection.
|
|
2780
|
+
Subscribe with `{ params, onEvent, onSync, onError?, onStatusChange? }` and use
|
|
2781
|
+
`onSync` to refetch after initial readiness and recovery. Each subscription
|
|
2782
|
+
exposes `unsubscribe()` and `getStatus()`. The client also exposes `getStatus()`,
|
|
2783
|
+
`resume()`, `close()`, and `getRequestHeaders()` for opt-in origin exclusion.
|
|
2784
|
+
Create a fresh client on user/workspace changes. Callbacks that already started
|
|
2785
|
+
remain application-owned work after unsubscribe.
|
|
2786
|
+
|
|
2787
|
+
`resolveBroadcastOrigin({ headers, principalId, tenantId, namespace })` binds
|
|
2788
|
+
the optional client header to authenticated server identity. Forward a captured
|
|
2789
|
+
origin through a job as `broadcastOrigin`, validate with `broadcastOriginSchema`
|
|
2790
|
+
when using Standard Schema directly, and pass it as `excludeOrigin` to publish.
|
|
2791
|
+
Use the same resolver on the stream endpoint. Missing, malformed, or anonymous
|
|
2792
|
+
origins disable exclusion. Default redaction hides origin headers and fields.
|
|
2793
|
+
|
|
2794
|
+
`defineBroadcastNotificationChannel({ channel, render })` from
|
|
2795
|
+
`@beignet/core/notifications` publishes rendered notifications through the port;
|
|
2796
|
+
return `undefined` to skip. Existing preferences and per-channel retries apply.
|
|
2797
|
+
A sent result means provider acceptance. Persistent inbox writes and their
|
|
2798
|
+
publication jobs must share an app-owned transaction; independent notification
|
|
2799
|
+
channels have no execution ordering guarantee.
|
|
2800
|
+
|
|
2801
|
+
The browser protocol has no replay IDs. Limits are 20 subscriptions, an 8 KiB
|
|
2802
|
+
encoded request, 64 KiB event data, 1 MiB buffers, 25-second heartbeats,
|
|
2803
|
+
10-second readiness waits, and a default stream lifetime of 60 seconds.
|
|
2804
|
+
`createBroadcastRoute` in `@beignet/next` or `@beignet/web` accepts
|
|
2805
|
+
`maxLifetimeMs` as a positive safe integer up to `3_600_000` (one hour), including
|
|
2806
|
+
`240_000` for four-minute streams. Leave setup/cleanup headroom below your
|
|
2807
|
+
hosting request deadline. Longer streams reduce renewal/refetch frequency and
|
|
2808
|
+
increase the interval between authorization checks.
|
|
2809
|
+
|
|
2810
|
+
The client honors the lifetime advertised in the first readiness message,
|
|
2811
|
+
with five seconds of watchdog grace measured from streaming response receipt.
|
|
2812
|
+
Additional readiness messages and heartbeats never extend the connection
|
|
2813
|
+
deadline. Every readiness message must include a valid `maxLifetimeMs`;
|
|
2814
|
+
missing or invalid lifetimes block subscriptions. Heartbeat loss closes a stream
|
|
2815
|
+
after 55 seconds without data, independently of its lifetime. Planned renewals still invoke `onSync`
|
|
2816
|
+
because events can be missed during reconnection. Terminal 4xx
|
|
2817
|
+
responses block until explicit resume; 429/5xx retry with jitter and respect
|
|
2818
|
+
`Retry-After`. Retry deadlines survive subscription changes. See the
|
|
2819
|
+
[broadcasting guide](https://beignetjs.com/broadcasting).
|
|
2820
|
+
|
|
2821
|
+
### Diagnose connection changes
|
|
2822
|
+
|
|
2823
|
+
`onSync(info)` and `onStatusChange(status, info)` receive
|
|
2824
|
+
`BroadcastConnectionInfo` from `@beignet/core/broadcasting/client`. Callbacks
|
|
2825
|
+
may omit the metadata argument when they do not need it.
|
|
2826
|
+
`createBroadcastQuerySubscription` forwards status metadata and accepts an
|
|
2827
|
+
optional `onSync(info)` observer; its reconciliation is still scheduled even
|
|
2828
|
+
when that observer throws. This notification describes readiness, not completion
|
|
2829
|
+
of queued or gated query refreshes. Do not refetch from that observer to bypass
|
|
2830
|
+
the gate accidentally.
|
|
2831
|
+
|
|
2832
|
+
| `info.reason` | What Beignet knows |
|
|
2833
|
+
| --- | --- |
|
|
2834
|
+
| `initial` | The first physical connection attempt. |
|
|
2835
|
+
| `planned-renewal` | The server explicitly announced renewal and then ended the stream normally without further traffic. |
|
|
2836
|
+
| `subscription-change` | Adding or removing a distinct channel restarted the multiplexed connection. |
|
|
2837
|
+
| `interruption` | A timeout, transport/protocol failure, or retryable rejection interrupted the connection. |
|
|
2838
|
+
| `unknown` | An unexplained EOF, explicit resume, or browser wakeup has no more precise known cause. |
|
|
2839
|
+
|
|
2840
|
+
Readiness reports the reason for that physical connection; another observer
|
|
2841
|
+
joining an already-ready channel sees the same information. Reconnecting status
|
|
2842
|
+
reports the known reason for restarting. An elapsed lifetime alone never proves
|
|
2843
|
+
planned renewal. Servers send an explicit, connection-wide renewal control frame
|
|
2844
|
+
before closing normally. An unexplained EOF stays `unknown`. A renewal frame
|
|
2845
|
+
followed by a stall produces `interruption` when a watchdog fires. Further traffic
|
|
2846
|
+
after the frame makes a later EOF `unknown`, including an incomplete frame.
|
|
2847
|
+
Reconciliation continues after **every** renewal because events have no replay.
|
|
2848
|
+
Planned renewal is a lifecycle change, not an `onError` failure.
|
|
2849
|
+
|
|
2850
|
+
Routes use the existing `broadcast` instrumentation watcher for admission,
|
|
2851
|
+
connection, and cleanup events. Server closure details report `planned-renewal`,
|
|
2852
|
+
`provider-disconnect`, `buffer-overflow`, `setup-failure`, or `unknown` as known
|
|
2853
|
+
locally; the server cannot infer why a browser cancelled. An optional
|
|
2854
|
+
`instrumentation` sink on `createBroadcastClient` uses the existing
|
|
2855
|
+
`ProviderInstrumentationPort` to record `broadcast.connecting`, `broadcast.ready`,
|
|
2856
|
+
and `broadcast.closed` with browser reasons. These events contain safe counts,
|
|
2857
|
+
durations, and reasons, never headers, credentials, payloads, or channel params.
|
|
2858
|
+
Keep custom logging equally restrained.
|
|
2859
|
+
|
|
2860
|
+
|
|
2746
2861
|
## License
|
|
2747
2862
|
|
|
2748
2863
|
MIT
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type ProviderInstrumentationPort } from "../providers/instrumentation.js";
|
|
2
|
+
import { type ChannelDefinition, type InferChannelEvent, type InferChannelParams } from "./index.js";
|
|
3
|
+
export type BroadcastClientStatus = "connecting" | "connected" | "reconnecting" | "blocked" | "closed";
|
|
4
|
+
/** Why this physical connection was opened, or why it is reconnecting. */
|
|
5
|
+
export interface BroadcastConnectionInfo {
|
|
6
|
+
readonly reason: "initial" | "planned-renewal" | "subscription-change" | "interruption" | "unknown";
|
|
7
|
+
}
|
|
8
|
+
/** Safe connection/protocol failure; application callback errors are reported separately. */
|
|
9
|
+
export declare class BroadcastClientError extends Error {
|
|
10
|
+
readonly retryable: boolean;
|
|
11
|
+
readonly status?: number | undefined;
|
|
12
|
+
constructor(message: string, retryable: boolean, status?: number | undefined);
|
|
13
|
+
}
|
|
14
|
+
export interface BroadcastClientSubscription {
|
|
15
|
+
unsubscribe(): void;
|
|
16
|
+
getStatus(): BroadcastClientStatus;
|
|
17
|
+
}
|
|
18
|
+
export interface BroadcastClient {
|
|
19
|
+
subscribe<C extends ChannelDefinition>(channel: C, options: {
|
|
20
|
+
params: InferChannelParams<C>;
|
|
21
|
+
onEvent: (event: InferChannelEvent<C>) => void | Promise<void>;
|
|
22
|
+
/** Refetch authoritative state after initial readiness and every recovered connection. */
|
|
23
|
+
onSync: (info: BroadcastConnectionInfo) => void | Promise<void>;
|
|
24
|
+
onError?: (error: unknown) => void;
|
|
25
|
+
onStatusChange?: (status: BroadcastClientStatus, info: BroadcastConnectionInfo) => void;
|
|
26
|
+
}): BroadcastClientSubscription;
|
|
27
|
+
/** Merge these optional headers into the app's typed HTTP client to opt into exclusion. */
|
|
28
|
+
getRequestHeaders(): Record<string, string>;
|
|
29
|
+
getStatus(): BroadcastClientStatus;
|
|
30
|
+
/** Explicit app action after credentials/access change. Renewals never unblock denied subscriptions. */
|
|
31
|
+
resume(): void;
|
|
32
|
+
close(): void;
|
|
33
|
+
}
|
|
34
|
+
export interface BroadcastClientOptions {
|
|
35
|
+
url: string;
|
|
36
|
+
/** Optional existing instrumentation sink; records causes and counts, never credentials or channel params. */
|
|
37
|
+
instrumentation?: ProviderInstrumentationPort;
|
|
38
|
+
/** Resolved afresh for every request. Never place credentials in the URL. */
|
|
39
|
+
headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);
|
|
40
|
+
credentials?: RequestCredentials;
|
|
41
|
+
fetch?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
|
42
|
+
}
|
|
43
|
+
/** One multiplexed streaming Fetch connection per instance. Create an instance per app auth session. */
|
|
44
|
+
export declare function createBroadcastClient(options: BroadcastClientOptions): BroadcastClient;
|
|
45
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/broadcasting/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,2BAA2B,EACjC,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAGL,KAAK,iBAAiB,EAEtB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EAGxB,MAAM,YAAY,CAAC;AAEpB,MAAM,MAAM,qBAAqB,GAC7B,YAAY,GACZ,WAAW,GACX,cAAc,GACd,SAAS,GACT,QAAQ,CAAC;AAEb,0EAA0E;AAC1E,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,MAAM,EACX,SAAS,GACT,iBAAiB,GACjB,qBAAqB,GACrB,cAAc,GACd,SAAS,CAAC;CACf;AAED,6FAA6F;AAC7F,qBAAa,oBAAqB,SAAQ,KAAK;IAG3C,QAAQ,CAAC,SAAS,EAAE,OAAO;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM;gBAFxB,OAAO,EAAE,MAAM,EACN,SAAS,EAAE,OAAO,EAClB,MAAM,CAAC,EAAE,MAAM,YAAA;CAK3B;AAED,MAAM,WAAW,2BAA2B;IAC1C,WAAW,IAAI,IAAI,CAAC;IACpB,SAAS,IAAI,qBAAqB,CAAC;CACpC;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,CAAC,CAAC,SAAS,iBAAiB,EACnC,OAAO,EAAE,CAAC,EACV,OAAO,EAAE;QACP,MAAM,EAAE,kBAAkB,CAAC,CAAC,CAAC,CAAC;QAC9B,OAAO,EAAE,CAAC,KAAK,EAAE,iBAAiB,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QAC/D,0FAA0F;QAC1F,MAAM,EAAE,CAAC,IAAI,EAAE,uBAAuB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QAChE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;QACnC,cAAc,CAAC,EAAE,CACf,MAAM,EAAE,qBAAqB,EAC7B,IAAI,EAAE,uBAAuB,KAC1B,IAAI,CAAC;KACX,GACA,2BAA2B,CAAC;IAC/B,2FAA2F;IAC3F,iBAAiB,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,SAAS,IAAI,qBAAqB,CAAC;IACnC,wGAAwG;IACxG,MAAM,IAAI,IAAI,CAAC;IACf,KAAK,IAAI,IAAI,CAAC;CACf;AAED,MAAM,WAAW,sBAAsB;IACrC,GAAG,EAAE,MAAM,CAAC;IACZ,8GAA8G;IAC9G,eAAe,CAAC,EAAE,2BAA2B,CAAC;IAC9C,6EAA6E;IAC7E,OAAO,CAAC,EAAE,WAAW,GAAG,CAAC,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC;IACnE,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,GAAG,GAAG,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;CAC7E;AA6DD,wGAAwG;AACxG,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,sBAAsB,GAC9B,eAAe,CAusBjB"}
|