@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
@@ -0,0 +1,87 @@
1
+ import { eventTransportValuesEqual, toEventTransportValue, } from "../events/transport.js";
2
+ /** Fixed protocol limits shared by clients, endpoints, and providers. */
3
+ export const broadcastLimits = Object.freeze({
4
+ subscriptions: 20,
5
+ requestBytes: 8_192,
6
+ payloadBytes: 65_536,
7
+ bufferedBytes: 1_048_576,
8
+ maxLifetimeMs: 60_000,
9
+ heartbeatMs: 25_000,
10
+ readyTimeoutMs: 10_000,
11
+ });
12
+ /** Invalid channel configuration or untrusted protocol input. */
13
+ export class BroadcastValidationError extends Error {
14
+ constructor(message) {
15
+ super(message);
16
+ this.name = "BroadcastValidationError";
17
+ }
18
+ }
19
+ const namePattern = /^[a-zA-Z][a-zA-Z0-9_.-]{0,127}$/;
20
+ export function defineChannel(name, options) {
21
+ if (!namePattern.test(name))
22
+ throw new BroadcastValidationError("Invalid broadcast channel name");
23
+ const events = Object.entries(options.events);
24
+ if (!events.length || events.some(([event]) => !namePattern.test(event))) {
25
+ throw new BroadcastValidationError(`Channel ${name} needs valid event names`);
26
+ }
27
+ return Object.freeze({
28
+ kind: "channel",
29
+ name,
30
+ params: options.params,
31
+ events: Object.freeze({ ...options.events }),
32
+ });
33
+ }
34
+ /** Validate both sides of a transport boundary; schema transforms must be stable. */
35
+ async function parseStable(schema, input, label) {
36
+ try {
37
+ const parsed = await schema["~standard"].validate(input);
38
+ if (parsed.issues)
39
+ throw new BroadcastValidationError(`Invalid ${label}`);
40
+ const canonical = toEventTransportValue(label, parsed.value);
41
+ const second = await schema["~standard"].validate(canonical);
42
+ if (second.issues ||
43
+ !eventTransportValuesEqual(canonical, toEventTransportValue(label, second.value))) {
44
+ throw new BroadcastValidationError(`Unstable transport schema for ${label}`);
45
+ }
46
+ return canonical;
47
+ }
48
+ catch (error) {
49
+ if (error instanceof BroadcastValidationError)
50
+ throw error;
51
+ throw new BroadcastValidationError(`Invalid ${label}`);
52
+ }
53
+ }
54
+ /** Provider and transport boundary validation. Uses parsed, canonical parameters. */
55
+ export async function parseChannelParams(channel, input) {
56
+ const parsed = await parseStable(channel.params, input, `${channel.name} parameters`);
57
+ if (!parsed ||
58
+ typeof parsed !== "object" ||
59
+ Array.isArray(parsed) ||
60
+ Object.values(parsed).some((value) => typeof value !== "string")) {
61
+ throw new BroadcastValidationError(`Channel ${channel.name} parameters must be a flat string record`);
62
+ }
63
+ if (new TextEncoder().encode(JSON.stringify(parsed)).byteLength >
64
+ broadcastLimits.requestBytes) {
65
+ throw new BroadcastValidationError(`Channel ${channel.name} parameters are too large`);
66
+ }
67
+ return parsed;
68
+ }
69
+ /** Provider and browser boundary validation. Event names select their schema. */
70
+ export async function parseChannelEvent(channel, input) {
71
+ if (!Object.hasOwn(channel.events, input.event))
72
+ throw new BroadcastValidationError(`Unknown event on channel ${channel.name}`);
73
+ const data = await parseStable(channel.events[input.event], input.data, `${channel.name}.${input.event}`);
74
+ if (new TextEncoder().encode(JSON.stringify(data)).byteLength >
75
+ broadcastLimits.payloadBytes) {
76
+ throw new BroadcastValidationError(`Channel ${channel.name} payload is too large`);
77
+ }
78
+ return { event: input.event, data };
79
+ }
80
+ /** Collision-free identity independent of parameter property order. Parse parameters first. */
81
+ export function channelKey(channel, params) {
82
+ return JSON.stringify([
83
+ channel,
84
+ Object.entries(params).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)),
85
+ ]);
86
+ }
87
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/broadcasting/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,83 @@
1
+ import "../server-only.js";
2
+ import type { StandardSchemaV1 } from "@standard-schema/spec";
3
+ import { type ChannelDefinition, type InferChannelEvent, type InferChannelParams } from "./index.js";
4
+ /** Server-authenticated scope captured at the originating HTTP boundary. JSON-safe for jobs. */
5
+ export interface BroadcastOrigin {
6
+ readonly version: 1;
7
+ readonly clientId: string;
8
+ readonly scope: string;
9
+ }
10
+ /** Validate an origin already captured by a trusted server for a job payload. */
11
+ export declare const broadcastOriginSchema: StandardSchemaV1<unknown, BroadcastOrigin>;
12
+ /** Never derive principal, tenant, or namespace from the optional client header. */
13
+ export declare function resolveBroadcastOrigin(options: {
14
+ headers: Headers;
15
+ principalId?: string | null;
16
+ tenantId?: string | null;
17
+ namespace: string;
18
+ }): BroadcastOrigin | undefined;
19
+ export type BroadcastPublication<C extends ChannelDefinition> = InferChannelEvent<C> & {
20
+ params: InferChannelParams<C>;
21
+ excludeOrigin?: BroadcastOrigin;
22
+ };
23
+ /** Readiness proves initial subscription only. Continuity loss requires reconnect and refetch. */
24
+ export interface BroadcastSubscription {
25
+ readonly ready: Promise<void>;
26
+ unsubscribe(): Promise<void>;
27
+ }
28
+ export interface BroadcastPort {
29
+ /** Resolves when accepted by the provider, including when nobody is subscribed. */
30
+ publish<C extends ChannelDefinition>(channel: C, publication: BroadcastPublication<C>): Promise<void>;
31
+ subscribe<C extends ChannelDefinition>(channel: C, options: {
32
+ params: InferChannelParams<C>;
33
+ onEvent: (event: InferChannelEvent<C>, metadata: {
34
+ excludeOrigin?: BroadcastOrigin;
35
+ }) => void;
36
+ onDisconnect: () => void;
37
+ }): BroadcastSubscription;
38
+ }
39
+ /** Internal transport envelope carried by broadcast providers, never a browser payload. */
40
+ export interface BroadcastEnvelope {
41
+ version: 1;
42
+ channel: string;
43
+ params: Record<string, string>;
44
+ event: string;
45
+ data: unknown;
46
+ excludeOrigin?: BroadcastOrigin;
47
+ }
48
+ /** Low-level adapter contract. Subscription callbacks must be isolated from publishers. */
49
+ export interface BroadcastTransport {
50
+ publish(key: string, envelope: BroadcastEnvelope): Promise<void>;
51
+ subscribe(key: string, options: {
52
+ onMessage: (envelope: unknown) => void;
53
+ onDisconnect: () => void;
54
+ }): BroadcastSubscription;
55
+ }
56
+ /** Shared validation and cleanup for provider implementations. */
57
+ export declare function createBroadcastPort(transport: BroadcastTransport): BroadcastPort;
58
+ export interface ChannelBinding<Ctx> {
59
+ readonly kind: "channel-binding";
60
+ readonly channel: ChannelDefinition;
61
+ authorize: (args: {
62
+ ctx: Ctx;
63
+ params: Record<string, string>;
64
+ }) => void | Promise<void>;
65
+ }
66
+ export interface ChannelRegistry<Ctx> {
67
+ readonly kind: "channel-registry";
68
+ readonly bindings: readonly ChannelBinding<Ctx>[];
69
+ get(name: string): ChannelBinding<Ctx> | undefined;
70
+ }
71
+ /** Bind every channel explicitly, including public channels. */
72
+ export declare function createBroadcasting<Ctx>(): {
73
+ defineChannelBinding: <C extends ChannelDefinition>(channel: C, options: {
74
+ authorize: (args: {
75
+ ctx: Ctx;
76
+ params: InferChannelParams<C>;
77
+ }) => void | Promise<void>;
78
+ }) => ChannelBinding<Ctx>;
79
+ defineChannelRegistry: (bindings: readonly ChannelBinding<Ctx>[]) => ChannelRegistry<Ctx>;
80
+ };
81
+ /** Validate a configurable deadline without allowing an unbounded stream. */
82
+ export declare function broadcastLifetime(value?: number): number;
83
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/broadcasting/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,213 @@
1
+ import "../server-only.js";
2
+ import { BroadcastValidationError, broadcastLimits, channelKey, parseChannelEvent, parseChannelParams, } from "./index.js";
3
+ const clientIdPattern = /^[a-zA-Z0-9_-]{22,128}$/;
4
+ function parseOrigin(value) {
5
+ if (!value || typeof value !== "object")
6
+ return undefined;
7
+ const candidate = value;
8
+ if (candidate.version !== 1 ||
9
+ typeof candidate.clientId !== "string" ||
10
+ !clientIdPattern.test(candidate.clientId) ||
11
+ typeof candidate.scope !== "string" ||
12
+ candidate.scope.length === 0 ||
13
+ candidate.scope.length > 2_048)
14
+ return undefined;
15
+ return { version: 1, clientId: candidate.clientId, scope: candidate.scope };
16
+ }
17
+ /** Validate an origin already captured by a trusted server for a job payload. */
18
+ export const broadcastOriginSchema = {
19
+ "~standard": {
20
+ version: 1,
21
+ vendor: "beignet",
22
+ validate(value) {
23
+ const origin = parseOrigin(value);
24
+ return origin
25
+ ? { value: origin }
26
+ : { issues: [{ message: "Invalid broadcast origin" }] };
27
+ },
28
+ },
29
+ };
30
+ /** Never derive principal, tenant, or namespace from the optional client header. */
31
+ export function resolveBroadcastOrigin(options) {
32
+ if (!options.principalId || !options.namespace)
33
+ return undefined;
34
+ return parseOrigin({
35
+ version: 1,
36
+ clientId: options.headers.get("X-Beignet-Broadcast-Client"),
37
+ scope: JSON.stringify([
38
+ options.namespace,
39
+ options.tenantId ?? null,
40
+ options.principalId,
41
+ ]),
42
+ });
43
+ }
44
+ /** Shared validation and cleanup for provider implementations. */
45
+ export function createBroadcastPort(transport) {
46
+ return {
47
+ async publish(channel, publication) {
48
+ const params = await parseChannelParams(channel, publication.params);
49
+ const event = await parseChannelEvent(channel, publication);
50
+ const excludeOrigin = publication.excludeOrigin === undefined
51
+ ? undefined
52
+ : parseOrigin(publication.excludeOrigin);
53
+ if (publication.excludeOrigin !== undefined && !excludeOrigin)
54
+ throw new BroadcastValidationError("Invalid broadcast origin");
55
+ await transport.publish(channelKey(channel.name, params), {
56
+ version: 1,
57
+ channel: channel.name,
58
+ params,
59
+ ...event,
60
+ ...(excludeOrigin ? { excludeOrigin } : {}),
61
+ });
62
+ },
63
+ subscribe(channel, options) {
64
+ let stopped = false;
65
+ let failed = false;
66
+ let subscription;
67
+ let queued = 0;
68
+ let queuedBytes = 0;
69
+ let pending = Promise.resolve();
70
+ let cancel;
71
+ let cleanup;
72
+ const release = () => (cleanup ??= subscription?.unsubscribe() ?? Promise.resolve());
73
+ const disconnect = () => {
74
+ if (stopped || failed)
75
+ return;
76
+ failed = true;
77
+ try {
78
+ options.onDisconnect();
79
+ }
80
+ catch {
81
+ /* Consumer callbacks cannot break provider lifecycle. */
82
+ }
83
+ };
84
+ const initialize = (async () => {
85
+ const params = await parseChannelParams(channel, options.params);
86
+ if (stopped)
87
+ return;
88
+ const key = channelKey(channel.name, params);
89
+ subscription = transport.subscribe(key, {
90
+ onDisconnect: disconnect,
91
+ onMessage(value) {
92
+ if (stopped || failed)
93
+ return;
94
+ let bytes;
95
+ try {
96
+ bytes = new TextEncoder().encode(JSON.stringify(value)).byteLength;
97
+ }
98
+ catch {
99
+ disconnect();
100
+ return;
101
+ }
102
+ if (bytes >
103
+ broadcastLimits.payloadBytes +
104
+ broadcastLimits.requestBytes +
105
+ 16_384 ||
106
+ queuedBytes + bytes > broadcastLimits.bufferedBytes) {
107
+ disconnect();
108
+ return;
109
+ }
110
+ if (++queued > 128) {
111
+ disconnect();
112
+ return;
113
+ }
114
+ queuedBytes += bytes;
115
+ pending = pending
116
+ .then(async () => {
117
+ if (stopped || failed)
118
+ return;
119
+ if (!value || typeof value !== "object")
120
+ throw new BroadcastValidationError("Invalid broadcast envelope");
121
+ const envelope = value;
122
+ if (envelope.version !== 1 || envelope.channel !== channel.name)
123
+ throw new BroadcastValidationError("Invalid broadcast envelope");
124
+ const receivedParams = await parseChannelParams(channel, envelope.params);
125
+ if (channelKey(channel.name, receivedParams) !== key)
126
+ throw new BroadcastValidationError("Broadcast channel mismatch");
127
+ const event = await parseChannelEvent(channel, envelope);
128
+ const excludeOrigin = envelope.excludeOrigin === undefined
129
+ ? undefined
130
+ : parseOrigin(envelope.excludeOrigin);
131
+ if (envelope.excludeOrigin !== undefined && !excludeOrigin)
132
+ throw new BroadcastValidationError("Invalid broadcast origin");
133
+ if (!stopped && !failed)
134
+ await options.onEvent(event, { excludeOrigin });
135
+ })
136
+ .catch(disconnect)
137
+ .finally(() => {
138
+ queued--;
139
+ queuedBytes -= bytes;
140
+ });
141
+ },
142
+ });
143
+ await subscription.ready;
144
+ if (stopped)
145
+ await release();
146
+ if (failed)
147
+ throw new Error("Broadcast subscription lost continuity before readiness");
148
+ })();
149
+ let timeout;
150
+ const ready = Promise.race([
151
+ initialize,
152
+ new Promise((_, reject) => {
153
+ cancel = () => reject(new Error("Broadcast subscription cancelled"));
154
+ timeout = setTimeout(() => reject(new Error("Broadcast subscription readiness timed out")), broadcastLimits.readyTimeoutMs);
155
+ }),
156
+ ])
157
+ .catch((error) => {
158
+ stopped = true;
159
+ void release().catch(() => undefined);
160
+ throw error;
161
+ })
162
+ .finally(() => {
163
+ if (timeout !== undefined)
164
+ clearTimeout(timeout);
165
+ cancel = undefined;
166
+ });
167
+ // Callers still observe rejection through ready; early cancellation must not leak a rejection.
168
+ void ready.catch(() => undefined);
169
+ return {
170
+ ready,
171
+ async unsubscribe() {
172
+ stopped = true;
173
+ cancel?.();
174
+ await ready.catch(() => undefined);
175
+ await release();
176
+ },
177
+ };
178
+ },
179
+ };
180
+ }
181
+ /** Bind every channel explicitly, including public channels. */
182
+ export function createBroadcasting() {
183
+ function defineChannelBinding(channel, options) {
184
+ return Object.freeze({
185
+ kind: "channel-binding",
186
+ channel,
187
+ authorize: ({ ctx, params, }) => options.authorize({ ctx, params: params }),
188
+ });
189
+ }
190
+ function defineChannelRegistry(bindings) {
191
+ const byName = new Map();
192
+ for (const binding of bindings) {
193
+ if (byName.has(binding.channel.name))
194
+ throw new BroadcastValidationError(`Duplicate channel ${binding.channel.name}`);
195
+ byName.set(binding.channel.name, binding);
196
+ }
197
+ return Object.freeze({
198
+ kind: "channel-registry",
199
+ bindings: Object.freeze([...bindings]),
200
+ get: (name) => byName.get(name),
201
+ });
202
+ }
203
+ return { defineChannelBinding, defineChannelRegistry };
204
+ }
205
+ /** Validate a configurable deadline without allowing an unbounded stream. */
206
+ export function broadcastLifetime(value = broadcastLimits.maxLifetimeMs) {
207
+ if (!Number.isInteger(value) ||
208
+ value < 1 ||
209
+ value > broadcastLimits.maxLifetimeMs)
210
+ throw new BroadcastValidationError("Broadcast maxLifetimeMs must be between 1 and 60000");
211
+ return value;
212
+ }
213
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/broadcasting/server.ts"],"names":[],"mappings":"AAAA,OAAO,mBAAmB,CAAC;AAE3B,OAAO,EACL,wBAAwB,EACxB,eAAe,EAEf,UAAU,EAGV,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,YAAY,CAAC;AASpB,MAAM,eAAe,GAAG,yBAAyB,CAAC;AAElD,SAAS,WAAW,CAAC,KAAc;IACjC,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IAC1D,MAAM,SAAS,GAAG,KAAgC,CAAC;IACnD,IACE,SAAS,CAAC,OAAO,KAAK,CAAC;QACvB,OAAO,SAAS,CAAC,QAAQ,KAAK,QAAQ;QACtC,CAAC,eAAe,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC;QACzC,OAAO,SAAS,CAAC,KAAK,KAAK,QAAQ;QACnC,SAAS,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC;QAC5B,SAAS,CAAC,KAAK,CAAC,MAAM,GAAG,KAAK;QAE9B,OAAO,SAAS,CAAC;IACnB,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,SAAS,CAAC,QAAQ,EAAE,KAAK,EAAE,SAAS,CAAC,KAAK,EAAE,CAAC;AAC9E,CAAC;AAED,iFAAiF;AACjF,MAAM,CAAC,MAAM,qBAAqB,GAChC;IACE,WAAW,EAAE;QACX,OAAO,EAAE,CAAC;QACV,MAAM,EAAE,SAAS;QACjB,QAAQ,CAAC,KAAK;YACZ,MAAM,MAAM,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;YAClC,OAAO,MAAM;gBACX,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE;gBACnB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,0BAA0B,EAAE,CAAC,EAAE,CAAC;QAC5D,CAAC;KACF;CACF,CAAC;AAEJ,oFAAoF;AACpF,MAAM,UAAU,sBAAsB,CAAC,OAKtC;IACC,IAAI,CAAC,OAAO,CAAC,WAAW,IAAI,CAAC,OAAO,CAAC,SAAS;QAAE,OAAO,SAAS,CAAC;IACjE,OAAO,WAAW,CAAC;QACjB,OAAO,EAAE,CAAC;QACV,QAAQ,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAC;QAC3D,KAAK,EAAE,IAAI,CAAC,SAAS,CAAC;YACpB,OAAO,CAAC,SAAS;YACjB,OAAO,CAAC,QAAQ,IAAI,IAAI;YACxB,OAAO,CAAC,WAAW;SACpB,CAAC;KACH,CAAC,CAAC;AACL,CAAC;AAuDD,kEAAkE;AAClE,MAAM,UAAU,mBAAmB,CACjC,SAA6B;IAE7B,OAAO;QACL,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,WAAW;YAChC,MAAM,MAAM,GAAG,MAAM,kBAAkB,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;YACrE,MAAM,KAAK,GAAG,MAAM,iBAAiB,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;YAC5D,MAAM,aAAa,GACjB,WAAW,CAAC,aAAa,KAAK,SAAS;gBACrC,CAAC,CAAC,SAAS;gBACX,CAAC,CAAC,WAAW,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC;YAC7C,IAAI,WAAW,CAAC,aAAa,KAAK,SAAS,IAAI,CAAC,aAAa;gBAC3D,MAAM,IAAI,wBAAwB,CAAC,0BAA0B,CAAC,CAAC;YACjE,MAAM,SAAS,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE;gBACxD,OAAO,EAAE,CAAC;gBACV,OAAO,EAAE,OAAO,CAAC,IAAI;gBACrB,MAAM;gBACN,GAAG,KAAK;gBACR,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAC5C,CAAC,CAAC;QACL,CAAC;QACD,SAAS,CAAC,OAAO,EAAE,OAAO;YACxB,IAAI,OAAO,GAAG,KAAK,CAAC;YACpB,IAAI,MAAM,GAAG,KAAK,CAAC;YACnB,IAAI,YAA+C,CAAC;YACpD,IAAI,MAAM,GAAG,CAAC,CAAC;YACf,IAAI,WAAW,GAAG,CAAC,CAAC;YACpB,IAAI,OAAO,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;YAChC,IAAI,MAAgC,CAAC;YACrC,IAAI,OAAkC,CAAC;YACvC,MAAM,OAAO,GAAG,GAAG,EAAE,CACnB,CAAC,OAAO,KAAK,YAAY,EAAE,WAAW,EAAE,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;YACjE,MAAM,UAAU,GAAG,GAAG,EAAE;gBACtB,IAAI,OAAO,IAAI,MAAM;oBAAE,OAAO;gBAC9B,MAAM,GAAG,IAAI,CAAC;gBACd,IAAI,CAAC;oBACH,OAAO,CAAC,YAAY,EAAE,CAAC;gBACzB,CAAC;gBAAC,MAAM,CAAC;oBACP,yDAAyD;gBAC3D,CAAC;YACH,CAAC,CAAC;YACF,MAAM,UAAU,GAAG,CAAC,KAAK,IAAI,EAAE;gBAC7B,MAAM,MAAM,GAAG,MAAM,kBAAkB,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;gBACjE,IAAI,OAAO;oBAAE,OAAO;gBACpB,MAAM,GAAG,GAAG,UAAU,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;gBAC7C,YAAY,GAAG,SAAS,CAAC,SAAS,CAAC,GAAG,EAAE;oBACtC,YAAY,EAAE,UAAU;oBACxB,SAAS,CAAC,KAAK;wBACb,IAAI,OAAO,IAAI,MAAM;4BAAE,OAAO;wBAC9B,IAAI,KAAa,CAAC;wBAClB,IAAI,CAAC;4BACH,KAAK,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAC9B,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CACtB,CAAC,UAAU,CAAC;wBACf,CAAC;wBAAC,MAAM,CAAC;4BACP,UAAU,EAAE,CAAC;4BACb,OAAO;wBACT,CAAC;wBACD,IACE,KAAK;4BACH,eAAe,CAAC,YAAY;gCAC1B,eAAe,CAAC,YAAY;gCAC5B,MAAM;4BACV,WAAW,GAAG,KAAK,GAAG,eAAe,CAAC,aAAa,EACnD,CAAC;4BACD,UAAU,EAAE,CAAC;4BACb,OAAO;wBACT,CAAC;wBACD,IAAI,EAAE,MAAM,GAAG,GAAG,EAAE,CAAC;4BACnB,UAAU,EAAE,CAAC;4BACb,OAAO;wBACT,CAAC;wBACD,WAAW,IAAI,KAAK,CAAC;wBACrB,OAAO,GAAG,OAAO;6BACd,IAAI,CAAC,KAAK,IAAI,EAAE;4BACf,IAAI,OAAO,IAAI,MAAM;gCAAE,OAAO;4BAC9B,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;gCACrC,MAAM,IAAI,wBAAwB,CAChC,4BAA4B,CAC7B,CAAC;4BACJ,MAAM,QAAQ,GAAG,KAA0B,CAAC;4BAC5C,IAAI,QAAQ,CAAC,OAAO,KAAK,CAAC,IAAI,QAAQ,CAAC,OAAO,KAAK,OAAO,CAAC,IAAI;gCAC7D,MAAM,IAAI,wBAAwB,CAChC,4BAA4B,CAC7B,CAAC;4BACJ,MAAM,cAAc,GAAG,MAAM,kBAAkB,CAC7C,OAAO,EACP,QAAQ,CAAC,MAAM,CAChB,CAAC;4BACF,IAAI,UAAU,CAAC,OAAO,CAAC,IAAI,EAAE,cAAc,CAAC,KAAK,GAAG;gCAClD,MAAM,IAAI,wBAAwB,CAChC,4BAA4B,CAC7B,CAAC;4BACJ,MAAM,KAAK,GAAG,MAAM,iBAAiB,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;4BACzD,MAAM,aAAa,GACjB,QAAQ,CAAC,aAAa,KAAK,SAAS;gCAClC,CAAC,CAAC,SAAS;gCACX,CAAC,CAAC,WAAW,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC;4BAC1C,IAAI,QAAQ,CAAC,aAAa,KAAK,SAAS,IAAI,CAAC,aAAa;gCACxD,MAAM,IAAI,wBAAwB,CAChC,0BAA0B,CAC3B,CAAC;4BACJ,IAAI,CAAC,OAAO,IAAI,CAAC,MAAM;gCACrB,MAAM,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,aAAa,EAAE,CAAC,CAAC;wBACpD,CAAC,CAAC;6BACD,KAAK,CAAC,UAAU,CAAC;6BACjB,OAAO,CAAC,GAAG,EAAE;4BACZ,MAAM,EAAE,CAAC;4BACT,WAAW,IAAI,KAAK,CAAC;wBACvB,CAAC,CAAC,CAAC;oBACP,CAAC;iBACF,CAAC,CAAC;gBACH,MAAM,YAAY,CAAC,KAAK,CAAC;gBACzB,IAAI,OAAO;oBAAE,MAAM,OAAO,EAAE,CAAC;gBAC7B,IAAI,MAAM;oBACR,MAAM,IAAI,KAAK,CACb,yDAAyD,CAC1D,CAAC;YACN,CAAC,CAAC,EAAE,CAAC;YACL,IAAI,OAAkD,CAAC;YACvD,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC;gBACzB,UAAU;gBACV,IAAI,OAAO,CAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE;oBAC/B,MAAM,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC,CAAC;oBACrE,OAAO,GAAG,UAAU,CAClB,GAAG,EAAE,CACH,MAAM,CAAC,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC,EACjE,eAAe,CAAC,cAAc,CAC/B,CAAC;gBACJ,CAAC,CAAC;aACH,CAAC;iBACC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;gBACf,OAAO,GAAG,IAAI,CAAC;gBACf,KAAK,OAAO,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;gBACtC,MAAM,KAAK,CAAC;YACd,CAAC,CAAC;iBACD,OAAO,CAAC,GAAG,EAAE;gBACZ,IAAI,OAAO,KAAK,SAAS;oBAAE,YAAY,CAAC,OAAO,CAAC,CAAC;gBACjD,MAAM,GAAG,SAAS,CAAC;YACrB,CAAC,CAAC,CAAC;YACL,+FAA+F;YAC/F,KAAK,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;YAClC,OAAO;gBACL,KAAK;gBACL,KAAK,CAAC,WAAW;oBACf,OAAO,GAAG,IAAI,CAAC;oBACf,MAAM,EAAE,EAAE,CAAC;oBACX,MAAM,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;oBACnC,MAAM,OAAO,EAAE,CAAC;gBAClB,CAAC;aACF,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC;AAiBD,gEAAgE;AAChE,MAAM,UAAU,kBAAkB;IAChC,SAAS,oBAAoB,CAC3B,OAAU,EACV,OAKC;QAED,OAAO,MAAM,CAAC,MAAM,CAAC;YACnB,IAAI,EAAE,iBAA0B;YAChC,OAAO;YACP,SAAS,EAAE,CAAC,EACV,GAAG,EACH,MAAM,GAIP,EAAE,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,MAA+B,EAAE,CAAC;SAC1E,CAAC,CAAC;IACL,CAAC;IACD,SAAS,qBAAqB,CAC5B,QAAwC;QAExC,MAAM,MAAM,GAAG,IAAI,GAAG,EAA+B,CAAC;QACtD,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;YAC/B,IAAI,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC;gBAClC,MAAM,IAAI,wBAAwB,CAChC,qBAAqB,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,CAC5C,CAAC;YACJ,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC5C,CAAC;QACD,OAAO,MAAM,CAAC,MAAM,CAAC;YACnB,IAAI,EAAE,kBAAkB;YACxB,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC;YACtC,GAAG,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC;SACxC,CAAC,CAAC;IACL,CAAC;IACD,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,CAAC;AACzD,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,iBAAiB,CAC/B,QAAgB,eAAe,CAAC,aAAa;IAE7C,IACE,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC;QACxB,KAAK,GAAG,CAAC;QACT,KAAK,GAAG,eAAe,CAAC,aAAa;QAErC,MAAM,IAAI,wBAAwB,CAChC,qDAAqD,CACtD,CAAC;IACJ,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -0,0 +1,40 @@
1
+ import "../server-only.js";
2
+ /** Non-secret, authenticated metadata. Supply the same expected context on reads. */
3
+ export type EncryptionContext = Readonly<Record<string, string>>;
4
+ /** Options shared by string encryption and decryption. */
5
+ export interface EncryptionValueOptions {
6
+ /** Plaintext for encrypt; the complete encrypted value for decrypt. */
7
+ value: string;
8
+ /** Bind the value to its purpose, tenant, or record. This is not authorization. */
9
+ context?: EncryptionContext;
10
+ }
11
+ /** Server-side authenticated string encryption. */
12
+ export interface EncryptionPort {
13
+ /** Encrypt a string; persist the complete returned value. */
14
+ encrypt(options: EncryptionValueOptions): Promise<string>;
15
+ /** Authenticate and decrypt a value with the same expected context. */
16
+ decrypt(options: EncryptionValueOptions): Promise<string>;
17
+ }
18
+ /** Configuration for the built-in AES-256-GCM implementation. */
19
+ export interface CreateEncryptionOptions {
20
+ /** A base64: prefixed, canonical Base64 encoding of 32 random bytes. */
21
+ key: string;
22
+ /** Decryption-only keys in the same format. New writes always use key. */
23
+ previousKeys?: readonly string[];
24
+ }
25
+ /** Malformed, unauthenticated, or undecryptable ciphertext. Contains no input. */
26
+ export declare class EncryptionDecryptionError extends Error {
27
+ /** Stable error name, independent of the decryption failure's cause. */
28
+ readonly name = "EncryptionDecryptionError";
29
+ constructor();
30
+ }
31
+ /** Generate a new 256-bit key. Store it in a server secret store; never log it. */
32
+ export declare function generateEncryptionKey(): string;
33
+ /**
34
+ * Create authenticated string encryption using Web Crypto AES-256-GCM.
35
+ * Configuration is validated synchronously. Each write uses a random 96-bit IV
36
+ * and a 128-bit authentication tag. Previous keys are used only for reads.
37
+ * No environment variables are read and no plaintext, keys, or context are logged.
38
+ */
39
+ export declare function createEncryption(options: CreateEncryptionOptions): EncryptionPort;
40
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/encryption/index.ts"],"names":[],"mappings":"AAAA,OAAO,mBAAmB,CAAC;AAE3B,qFAAqF;AACrF,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAEjE,0DAA0D;AAC1D,MAAM,WAAW,sBAAsB;IACrC,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAC;IACd,mFAAmF;IACnF,OAAO,CAAC,EAAE,iBAAiB,CAAC;CAC7B;AAED,mDAAmD;AACnD,MAAM,WAAW,cAAc;IAC7B,6DAA6D;IAC7D,OAAO,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1D,uEAAuE;IACvE,OAAO,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAC3D;AAED,iEAAiE;AACjE,MAAM,WAAW,uBAAuB;IACtC,wEAAwE;IACxE,GAAG,EAAE,MAAM,CAAC;IACZ,0EAA0E;IAC1E,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAClC;AAED,kFAAkF;AAClF,qBAAa,yBAA0B,SAAQ,KAAK;IAClD,wEAAwE;IACxE,QAAQ,CAAC,IAAI,+BAA+B;;CAK7C;AAsED,mFAAmF;AACnF,wBAAgB,qBAAqB,IAAI,MAAM,CAE9C;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,uBAAuB,GAC/B,cAAc,CA6EhB"}
@@ -0,0 +1,134 @@
1
+ import "../server-only.js";
2
+ /** Malformed, unauthenticated, or undecryptable ciphertext. Contains no input. */
3
+ export class EncryptionDecryptionError extends Error {
4
+ /** Stable error name, independent of the decryption failure's cause. */
5
+ name = "EncryptionDecryptionError";
6
+ constructor() {
7
+ super("Unable to decrypt encrypted value.");
8
+ }
9
+ }
10
+ const envelopePrefix = "beignet:enc:v1:";
11
+ const ivLength = 12;
12
+ const tagLength = 16;
13
+ const encoder = new TextEncoder();
14
+ const decoder = new TextDecoder("utf-8", { fatal: true });
15
+ function encodeBase64(bytes) {
16
+ // Bound each spread so large values do not overflow the argument stack.
17
+ const chunks = [];
18
+ for (let offset = 0; offset < bytes.length; offset += 8192) {
19
+ chunks.push(String.fromCharCode(...bytes.subarray(offset, offset + 8192)));
20
+ }
21
+ return btoa(chunks.join(""));
22
+ }
23
+ function decodeBase64(value) {
24
+ if (value.length % 4 !== 0 || !/^[A-Za-z0-9+/]*={0,2}$/.test(value)) {
25
+ throw new Error("Invalid Base64 encoding.");
26
+ }
27
+ const bytes = Uint8Array.from(atob(value), (character) => character.charCodeAt(0));
28
+ if (encodeBase64(bytes) !== value)
29
+ throw new Error("Noncanonical Base64 encoding.");
30
+ return bytes;
31
+ }
32
+ function decodeKey(value, label) {
33
+ try {
34
+ if (typeof value !== "string" ||
35
+ value.length !== 51 ||
36
+ !value.startsWith("base64:"))
37
+ throw new Error();
38
+ const bytes = decodeBase64(value.slice(7));
39
+ if (bytes.length !== 32)
40
+ throw new Error();
41
+ return bytes;
42
+ }
43
+ catch {
44
+ throw new TypeError(`${label} must be base64: followed by the Base64 encoding of 32 bytes. Use generateEncryptionKey().`);
45
+ }
46
+ }
47
+ function authenticatedData(context) {
48
+ if (context !== undefined &&
49
+ (context === null ||
50
+ typeof context !== "object" ||
51
+ (Object.getPrototypeOf(context) !== Object.prototype &&
52
+ Object.getPrototypeOf(context) !== null))) {
53
+ throw new TypeError("Encryption context must be a plain record of strings.");
54
+ }
55
+ const entries = Object.entries(context ?? {}).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0);
56
+ if (entries.some(([, value]) => typeof value !== "string")) {
57
+ throw new TypeError("Encryption context values must be strings.");
58
+ }
59
+ return encoder.encode(JSON.stringify([envelopePrefix, entries]));
60
+ }
61
+ /** Generate a new 256-bit key. Store it in a server secret store; never log it. */
62
+ export function generateEncryptionKey() {
63
+ return `base64:${encodeBase64(crypto.getRandomValues(new Uint8Array(32)))}`;
64
+ }
65
+ /**
66
+ * Create authenticated string encryption using Web Crypto AES-256-GCM.
67
+ * Configuration is validated synchronously. Each write uses a random 96-bit IV
68
+ * and a 128-bit authentication tag. Previous keys are used only for reads.
69
+ * No environment variables are read and no plaintext, keys, or context are logged.
70
+ */
71
+ export function createEncryption(options) {
72
+ const current = decodeKey(options?.key, "Encryption key");
73
+ if (options.previousKeys !== undefined &&
74
+ !Array.isArray(options.previousKeys)) {
75
+ throw new TypeError("Encryption previousKeys must be an array of keys.");
76
+ }
77
+ const rawKeys = [
78
+ current,
79
+ ...Array.from(options.previousKeys ?? [], (key, index) => decodeKey(key, `Encryption previousKeys[${index}]`)),
80
+ ];
81
+ const importedKeys = [];
82
+ const getKey = (index) => {
83
+ const existing = importedKeys[index];
84
+ if (existing)
85
+ return existing;
86
+ const imported = crypto.subtle.importKey("raw", rawKeys[index], "AES-GCM", false, ["encrypt", "decrypt"]);
87
+ importedKeys[index] = imported;
88
+ return imported;
89
+ };
90
+ return {
91
+ async encrypt({ value, context }) {
92
+ if (typeof value !== "string")
93
+ throw new TypeError("Encryption value must be a string.");
94
+ const additionalData = authenticatedData(context);
95
+ const iv = crypto.getRandomValues(new Uint8Array(ivLength));
96
+ const ciphertext = new Uint8Array(await crypto.subtle.encrypt({ name: "AES-GCM", iv, additionalData, tagLength: tagLength * 8 }, await getKey(0),
97
+ // JSON preserves all JavaScript strings, including lone UTF-16 surrogates.
98
+ encoder.encode(JSON.stringify(value))));
99
+ const envelope = new Uint8Array(iv.length + ciphertext.length);
100
+ envelope.set(iv);
101
+ envelope.set(ciphertext, iv.length);
102
+ return `${envelopePrefix}${encodeBase64(envelope)}`;
103
+ },
104
+ async decrypt(options) {
105
+ try {
106
+ const { value, context } = options;
107
+ if (typeof value !== "string" || !value.startsWith(envelopePrefix))
108
+ throw new Error();
109
+ const envelope = decodeBase64(value.slice(envelopePrefix.length));
110
+ if (envelope.length < ivLength + tagLength + 2)
111
+ throw new Error();
112
+ const additionalData = authenticatedData(context);
113
+ const iv = envelope.slice(0, ivLength);
114
+ const ciphertext = envelope.slice(ivLength);
115
+ for (let index = 0; index < rawKeys.length; index++) {
116
+ try {
117
+ const plaintext = await crypto.subtle.decrypt({ name: "AES-GCM", iv, additionalData, tagLength: tagLength * 8 }, await getKey(index), ciphertext);
118
+ const parsed = JSON.parse(decoder.decode(plaintext));
119
+ if (typeof parsed === "string")
120
+ return parsed;
121
+ }
122
+ catch {
123
+ // Try only the configured key ring; never return unauthenticated data.
124
+ }
125
+ }
126
+ }
127
+ catch {
128
+ // All decryption failures share one non-sensitive public error.
129
+ }
130
+ throw new EncryptionDecryptionError();
131
+ },
132
+ };
133
+ }
134
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/encryption/index.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,6 @@
1
1
  import type { StandardSchemaV1 } from "@standard-schema/spec";
2
+ import type { ChannelDefinition } from "../broadcasting/index.js";
3
+ import type { BroadcastPort, BroadcastPublication } from "../broadcasting/server.js";
2
4
  import { type JobDef, type JobDispatcher, type JobHook, type JobRetryOptions, type JobTimeoutDuration } from "../jobs/index.js";
3
5
  import type { SendMailOptions } from "../mail/index.js";
4
6
  import type { ProviderInstrumentationTarget } from "../providers/index.js";
@@ -557,6 +559,18 @@ export declare function createQueuedNotificationsProvider<Name extends string, C
557
559
  * example when a recipient does not have an email address.
558
560
  */
559
561
  export declare function defineMailNotificationChannel<Payload extends StandardSchema, Ctx extends MailNotificationContext>(render: MailNotificationRenderer<Payload, Ctx>): NotificationChannelHandler<Payload, Ctx>;
562
+ /** Deliver a typed browser hint through the existing notification delivery pipeline.
563
+ * `sent` means provider acceptance, including when the recipient is offline.
564
+ * Return undefined to skip. Persistent inbox writes and their outbox ordering belong to the app.
565
+ */
566
+ export declare function defineBroadcastNotificationChannel<C extends ChannelDefinition, Payload extends StandardSchema, Ctx extends {
567
+ ports: {
568
+ broadcast: BroadcastPort;
569
+ };
570
+ }>(options: {
571
+ channel: C;
572
+ render: (args: NotificationChannelHandleArgs<Payload, Ctx>) => MaybePromise<BroadcastPublication<NoInfer<C>> | undefined>;
573
+ }): NotificationChannelHandler<Payload, Ctx>;
560
574
  /**
561
575
  * Create an in-memory notification port for tests and examples.
562
576
  *