@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.
Files changed (48) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +116 -2
  3. package/dist/broadcasting/client.d.ts +38 -0
  4. package/dist/broadcasting/client.d.ts.map +1 -0
  5. package/dist/broadcasting/client.js +519 -0
  6. package/dist/broadcasting/client.js.map +1 -0
  7. package/dist/broadcasting/index.d.ts +43 -0
  8. package/dist/broadcasting/index.d.ts.map +1 -0
  9. package/dist/broadcasting/index.js +87 -0
  10. package/dist/broadcasting/index.js.map +1 -0
  11. package/dist/broadcasting/server.d.ts +83 -0
  12. package/dist/broadcasting/server.d.ts.map +1 -0
  13. package/dist/broadcasting/server.js +213 -0
  14. package/dist/broadcasting/server.js.map +1 -0
  15. package/dist/encryption/index.d.ts +40 -0
  16. package/dist/encryption/index.d.ts.map +1 -0
  17. package/dist/encryption/index.js +134 -0
  18. package/dist/encryption/index.js.map +1 -0
  19. package/dist/notifications/index.d.ts +14 -0
  20. package/dist/notifications/index.d.ts.map +1 -1
  21. package/dist/notifications/index.js +17 -0
  22. package/dist/notifications/index.js.map +1 -1
  23. package/dist/ports/redaction.d.ts +1 -1
  24. package/dist/ports/redaction.d.ts.map +1 -1
  25. package/dist/ports/redaction.js +3 -0
  26. package/dist/ports/redaction.js.map +1 -1
  27. package/dist/server/request-executor.d.ts.map +1 -1
  28. package/dist/server/request-executor.js +13 -0
  29. package/dist/server/request-executor.js.map +1 -1
  30. package/dist/server/server.d.ts +4 -2
  31. package/dist/server/server.d.ts.map +1 -1
  32. package/dist/server/server.js +1 -1
  33. package/dist/server/server.js.map +1 -1
  34. package/dist/server/use-case-route.d.ts +82 -17
  35. package/dist/server/use-case-route.d.ts.map +1 -1
  36. package/dist/server/use-case-route.js +40 -4
  37. package/dist/server/use-case-route.js.map +1 -1
  38. package/package.json +17 -1
  39. package/skills/app-architecture/SKILL.md +48 -1
  40. package/src/broadcasting/client.ts +714 -0
  41. package/src/broadcasting/index.ts +173 -0
  42. package/src/broadcasting/server.ts +352 -0
  43. package/src/encryption/index.ts +198 -0
  44. package/src/notifications/index.ts +32 -0
  45. package/src/ports/redaction.ts +3 -0
  46. package/src/server/request-executor.ts +19 -0
  47. package/src/server/server.ts +8 -3
  48. 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"}