@beignet/core 0.0.52 → 0.0.54
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 +20 -0
- package/README.md +116 -2
- package/dist/broadcasting/client.d.ts +38 -0
- package/dist/broadcasting/client.d.ts.map +1 -0
- package/dist/broadcasting/client.js +519 -0
- package/dist/broadcasting/client.js.map +1 -0
- package/dist/broadcasting/index.d.ts +43 -0
- package/dist/broadcasting/index.d.ts.map +1 -0
- package/dist/broadcasting/index.js +87 -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/encryption/index.d.ts +40 -0
- package/dist/encryption/index.d.ts.map +1 -0
- package/dist/encryption/index.js +134 -0
- package/dist/encryption/index.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/request-executor.d.ts.map +1 -1
- package/dist/server/request-executor.js +13 -0
- package/dist/server/request-executor.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/dist/server/use-case-route.d.ts +82 -17
- package/dist/server/use-case-route.d.ts.map +1 -1
- package/dist/server/use-case-route.js +40 -4
- package/dist/server/use-case-route.js.map +1 -1
- package/package.json +17 -1
- package/skills/app-architecture/SKILL.md +48 -1
- package/src/broadcasting/client.ts +714 -0
- package/src/broadcasting/index.ts +173 -0
- package/src/broadcasting/server.ts +352 -0
- package/src/encryption/index.ts +198 -0
- package/src/notifications/index.ts +32 -0
- package/src/ports/redaction.ts +3 -0
- package/src/server/request-executor.ts +19 -0
- package/src/server/server.ts +8 -3
- package/src/server/use-case-route.ts +234 -27
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @beignet/core
|
|
2
2
|
|
|
3
|
+
## 0.0.54
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- b71b34f: Add typed, authorized browser broadcasting with bounded SSE connections, memory
|
|
8
|
+
and Redis providers, reconnect reconciliation, React Query invalidation,
|
|
9
|
+
initiating-client exclusion, notification delivery, and transactional inbox
|
|
10
|
+
scaffolding. Raw routes now preserve context added by route hooks.
|
|
11
|
+
|
|
12
|
+
## 0.0.53
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- 6ef881c: Reject incompatible default route-binder inputs at compile time and classify
|
|
17
|
+
type-erased runtime mismatches as framework-owned validation errors. Generated
|
|
18
|
+
resource cursors now remain optional at the request boundary and normalize to
|
|
19
|
+
nullable repository pagination state inside the use case.
|
|
20
|
+
- e1a559d: Add authenticated string encryption through EncryptionPort and createEncryption, with optional context binding, decryption through previous keys, and non-sensitive failure errors. Add beignet encryption key for key generation, server import checks, and guidance for rotation and recovery.
|
|
21
|
+
- edebbea: Add `@beignet/react-form`, a TanStack Form adapter that installs a contract body schema as a Standard Schema validator, types the `onServer` error slot, and maps client errors with `serverErrorMap`. `beignet lint` keeps the form packages out of domain and use-case code, `beignet doctor` expects `client/forms.ts` for either form adapter, and `beignet make feature --with ui` refuses to add React Hook Form components to an app that depends on `@beignet/react-form`.
|
|
22
|
+
|
|
3
23
|
## 0.0.52
|
|
4
24
|
|
|
5
25
|
### 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, 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,11 +50,15 @@ 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 |
|
|
56
59
|
| `@beignet/core/contracts` | HTTP contract builders, types, path helpers, and contract metadata |
|
|
57
60
|
| `@beignet/core/domain` | Entities, value objects, and domain events |
|
|
61
|
+
| `@beignet/core/encryption` | Authenticated string encryption, EncryptionPort, and key generation |
|
|
58
62
|
| `@beignet/core/entitlements` | Product access decision types, helpers, and static entitlement adapter |
|
|
59
63
|
| `@beignet/core/error-reporting` | Error reporting port, memory adapter, no-op adapter, and helpers |
|
|
60
64
|
| `@beignet/core/errors` | Error catalogs and response helpers |
|
|
@@ -85,6 +89,49 @@ name the framework area they depend on.
|
|
|
85
89
|
| `@beignet/core/uploads/client` | Browser upload client for server and direct uploads |
|
|
86
90
|
| `@beignet/core/webhooks` | Inbound webhook definitions, verifiers, memory test verifier, and HMAC verifier |
|
|
87
91
|
|
|
92
|
+
## Encryption
|
|
93
|
+
|
|
94
|
+
Use `createEncryption(...)` in server infra and expose `EncryptionPort` through
|
|
95
|
+
your app ports. It uses Web Crypto AES-256-GCM with a random 96-bit IV and a
|
|
96
|
+
128-bit authentication tag for each write.
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
import { createEncryption } from "@beignet/core/encryption";
|
|
100
|
+
import { env } from "@/lib/env";
|
|
101
|
+
|
|
102
|
+
// Supply a persistent server secret from your validated environment config.
|
|
103
|
+
const encryption = createEncryption({
|
|
104
|
+
key: env.ENCRYPTION_KEY,
|
|
105
|
+
previousKeys: env.ENCRYPTION_PREVIOUS_KEYS,
|
|
106
|
+
});
|
|
107
|
+
const context = { purpose: "integration-token", tenantId: "tenant-1" };
|
|
108
|
+
const encrypted = await encryption.encrypt({ value: "example-token", context });
|
|
109
|
+
const token = await encryption.decrypt({ value: encrypted, context });
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Generate a persistent key with `beignet encryption key` or
|
|
113
|
+
`generateEncryptionKey()`: canonical `base64:` plus 32 random bytes. All keys
|
|
114
|
+
are validated synchronously. `key` encrypts new writes; `previousKeys` is an
|
|
115
|
+
optional array used only for decryption. The methods accept strings and return
|
|
116
|
+
`Promise<string>`. Optional `context` is a plain string record authenticated
|
|
117
|
+
with the value; supply the same expected context when decrypting. It does not
|
|
118
|
+
replace authorization.
|
|
119
|
+
|
|
120
|
+
Store the complete opaque ciphertext in a text column. Malformed values,
|
|
121
|
+
tampering, wrong keys, and context mismatches reject with
|
|
122
|
+
`EncryptionDecryptionError` and a non-sensitive message. There is no plaintext
|
|
123
|
+
fallback. The API emits no logs or telemetry.
|
|
124
|
+
|
|
125
|
+
Keep keys outside client bundles and logs. Rotation does not rewrite existing
|
|
126
|
+
data. Distribute a new decryption key to all readers before switching writes,
|
|
127
|
+
retain old keys while records and backups require them, and test recovery.
|
|
128
|
+
Losing a required key makes its ciphertext unreadable. This is field encryption,
|
|
129
|
+
not password hashing or streaming file encryption. See the
|
|
130
|
+
[encryption guide](https://beignetjs.com/encryption) for port wiring, rotation,
|
|
131
|
+
recovery, and KMS boundaries.
|
|
132
|
+
|
|
133
|
+
## Import boundaries
|
|
134
|
+
|
|
88
135
|
Use boundary markers as side-effect imports so local linting and formatting do
|
|
89
136
|
not treat them as unused symbols:
|
|
90
137
|
|
|
@@ -1132,7 +1179,7 @@ contracts should stay on explicit paths; catch-all contract patterns such as
|
|
|
1132
1179
|
For routes that cannot be contracts at all — third-party callback endpoints
|
|
1133
1180
|
with externally defined request shapes, signature-verified webhooks,
|
|
1134
1181
|
streaming endpoints that own body consumption —
|
|
1135
|
-
`server.rawRoute({ name, method, path, metadata }).handle(fn)` builds a
|
|
1182
|
+
`server.rawRoute({ name, method, path, metadata, hooks }).handle(fn)` builds a
|
|
1136
1183
|
handler that still runs the whole pipeline (hooks, context creation,
|
|
1137
1184
|
instrumentation, framework error mapping) without contract parsing or
|
|
1138
1185
|
validation. The request body stays unconsumed for the handler, `metadata`
|
|
@@ -2226,6 +2273,13 @@ explicit `input: (parts) => ...` mapper. When the use case `.input(...)`
|
|
|
2226
2273
|
schema is the contract's sole request schema by reference, the server skips
|
|
2227
2274
|
the use case's input re-parse — one schema, one parse.
|
|
2228
2275
|
|
|
2276
|
+
The route definition checks the inferred default binder input against the use
|
|
2277
|
+
case input type. Incompatible concrete shapes are compile errors with a prompt
|
|
2278
|
+
to add an explicit `input` mapper. If route types are deliberately erased and
|
|
2279
|
+
the default input still fails use-case validation at runtime, the server
|
|
2280
|
+
returns a framework-owned `500 USE_CASE_INPUT_VALIDATION_ERROR` rather than
|
|
2281
|
+
passing the failure through the app's unhandled-error mapper.
|
|
2282
|
+
|
|
2229
2283
|
Use `{ contract, handle }` as the escape hatch for response headers,
|
|
2230
2284
|
streaming, and multi-status responses. `defineRoute` remains
|
|
2231
2285
|
available for full handlers that read hook-added `ctx` fields.
|
|
@@ -2687,10 +2741,70 @@ field requiredness, descriptions, and constraints.
|
|
|
2687
2741
|
- [`@beignet/next`](https://beignetjs.com/server) - Next.js server adapter
|
|
2688
2742
|
- [`@beignet/react-query`](https://beignetjs.com/react-query) - TanStack Query integration
|
|
2689
2743
|
- [`@beignet/react-hook-form`](https://beignetjs.com/react-hook-form) - React Hook Form integration
|
|
2744
|
+
- [`@beignet/react-form`](https://beignetjs.com/react-form) - TanStack Form integration
|
|
2690
2745
|
- [`@beignet/react-uploads`](https://beignetjs.com/react-uploads) - React upload state and progress hooks
|
|
2691
2746
|
- [`@beignet/nuqs`](https://beignetjs.com/nuqs) - URL query state integration with nuqs
|
|
2692
2747
|
- [`@beignet/devtools`](https://beignetjs.com/devtools) - Local request, provider, and audit timeline
|
|
2693
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 streams renewed within 60 seconds. Terminal 4xx
|
|
2804
|
+
responses block until explicit resume; 429/5xx retry with jitter and respect
|
|
2805
|
+
`Retry-After`. Retry deadlines survive subscription changes. See the
|
|
2806
|
+
[broadcasting guide](https://beignetjs.com/broadcasting).
|
|
2807
|
+
|
|
2694
2808
|
## License
|
|
2695
2809
|
|
|
2696
2810
|
MIT
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type ChannelDefinition, type InferChannelEvent, type InferChannelParams } from "./index.js";
|
|
2
|
+
export type BroadcastClientStatus = "connecting" | "connected" | "reconnecting" | "blocked" | "closed";
|
|
3
|
+
/** Safe connection/protocol failure; application callback errors are reported separately. */
|
|
4
|
+
export declare class BroadcastClientError extends Error {
|
|
5
|
+
readonly retryable: boolean;
|
|
6
|
+
readonly status?: number | undefined;
|
|
7
|
+
constructor(message: string, retryable: boolean, status?: number | undefined);
|
|
8
|
+
}
|
|
9
|
+
export interface BroadcastClientSubscription {
|
|
10
|
+
unsubscribe(): void;
|
|
11
|
+
getStatus(): BroadcastClientStatus;
|
|
12
|
+
}
|
|
13
|
+
export interface BroadcastClient {
|
|
14
|
+
subscribe<C extends ChannelDefinition>(channel: C, options: {
|
|
15
|
+
params: InferChannelParams<C>;
|
|
16
|
+
onEvent: (event: InferChannelEvent<C>) => void | Promise<void>;
|
|
17
|
+
/** Refetch authoritative state after initial readiness and every recovered connection. */
|
|
18
|
+
onSync: () => void | Promise<void>;
|
|
19
|
+
onError?: (error: unknown) => void;
|
|
20
|
+
onStatusChange?: (status: BroadcastClientStatus) => void;
|
|
21
|
+
}): BroadcastClientSubscription;
|
|
22
|
+
/** Merge these optional headers into the app's typed HTTP client to opt into exclusion. */
|
|
23
|
+
getRequestHeaders(): Record<string, string>;
|
|
24
|
+
getStatus(): BroadcastClientStatus;
|
|
25
|
+
/** Explicit app action after credentials/access change. Renewals never unblock denied subscriptions. */
|
|
26
|
+
resume(): void;
|
|
27
|
+
close(): void;
|
|
28
|
+
}
|
|
29
|
+
export interface BroadcastClientOptions {
|
|
30
|
+
url: string;
|
|
31
|
+
/** Resolved afresh for every request. Never place credentials in the URL. */
|
|
32
|
+
headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);
|
|
33
|
+
credentials?: RequestCredentials;
|
|
34
|
+
fetch?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
|
35
|
+
}
|
|
36
|
+
/** One multiplexed streaming Fetch connection per instance. Create an instance per app auth session. */
|
|
37
|
+
export declare function createBroadcastClient(options: BroadcastClientOptions): BroadcastClient;
|
|
38
|
+
//# 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,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,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,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QACnC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;QACnC,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,qBAAqB,KAAK,IAAI,CAAC;KAC1D,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,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;AAyDD,wGAAwG;AACxG,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,sBAAsB,GAC9B,eAAe,CAglBjB"}
|