@beignet/devtools 0.0.1

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 (68) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/README.md +464 -0
  3. package/dist/access.d.ts +21 -0
  4. package/dist/access.d.ts.map +1 -0
  5. package/dist/access.js +20 -0
  6. package/dist/access.js.map +1 -0
  7. package/dist/audit.d.ts +10 -0
  8. package/dist/audit.d.ts.map +1 -0
  9. package/dist/audit.js +49 -0
  10. package/dist/audit.js.map +1 -0
  11. package/dist/events.d.ts +143 -0
  12. package/dist/events.d.ts.map +1 -0
  13. package/dist/events.js +20 -0
  14. package/dist/events.js.map +1 -0
  15. package/dist/index.d.ts +114 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +44 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/instrumentation.d.ts +74 -0
  20. package/dist/instrumentation.d.ts.map +1 -0
  21. package/dist/instrumentation.js +293 -0
  22. package/dist/instrumentation.js.map +1 -0
  23. package/dist/persistence.d.ts +30 -0
  24. package/dist/persistence.d.ts.map +1 -0
  25. package/dist/persistence.js +100 -0
  26. package/dist/persistence.js.map +1 -0
  27. package/dist/provider-instrumentation.d.ts +9 -0
  28. package/dist/provider-instrumentation.d.ts.map +1 -0
  29. package/dist/provider-instrumentation.js +25 -0
  30. package/dist/provider-instrumentation.js.map +1 -0
  31. package/dist/provider.d.ts +79 -0
  32. package/dist/provider.d.ts.map +1 -0
  33. package/dist/provider.js +293 -0
  34. package/dist/provider.js.map +1 -0
  35. package/dist/redaction.d.ts +5 -0
  36. package/dist/redaction.d.ts.map +1 -0
  37. package/dist/redaction.js +20 -0
  38. package/dist/redaction.js.map +1 -0
  39. package/dist/routes.d.ts +113 -0
  40. package/dist/routes.d.ts.map +1 -0
  41. package/dist/routes.js +247 -0
  42. package/dist/routes.js.map +1 -0
  43. package/dist/trace-context.d.ts +29 -0
  44. package/dist/trace-context.d.ts.map +1 -0
  45. package/dist/trace-context.js +74 -0
  46. package/dist/trace-context.js.map +1 -0
  47. package/dist/ui.d.ts +14 -0
  48. package/dist/ui.d.ts.map +1 -0
  49. package/dist/ui.js +795 -0
  50. package/dist/ui.js.map +1 -0
  51. package/dist/watchers.d.ts +22 -0
  52. package/dist/watchers.d.ts.map +1 -0
  53. package/dist/watchers.js +171 -0
  54. package/dist/watchers.js.map +1 -0
  55. package/package.json +66 -0
  56. package/src/access.ts +52 -0
  57. package/src/audit.ts +71 -0
  58. package/src/events.ts +193 -0
  59. package/src/index.ts +136 -0
  60. package/src/instrumentation.ts +451 -0
  61. package/src/persistence.ts +163 -0
  62. package/src/provider-instrumentation.ts +50 -0
  63. package/src/provider.ts +375 -0
  64. package/src/redaction.ts +26 -0
  65. package/src/routes.ts +317 -0
  66. package/src/trace-context.ts +115 -0
  67. package/src/ui.ts +807 -0
  68. package/src/watchers.ts +235 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # @beignet/devtools
2
+
3
+ ## 0.0.1
4
+
5
+ - Initial Beignet release under the `@beignet` npm scope.
package/README.md ADDED
@@ -0,0 +1,464 @@
1
+ # @beignet/devtools
2
+
3
+ Development-time event timeline for Beignet apps. It records HTTP requests,
4
+ errors, use case runs, domain events, jobs, scheduled tasks, and provider
5
+ activity in a bounded in-memory buffer, then serves a live dashboard from your
6
+ app.
7
+
8
+ Devtools is enabled outside production by default and returns a no-op port in
9
+ production unless you explicitly enable it.
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ bun add @beignet/devtools
15
+ ```
16
+
17
+ ## Next.js setup
18
+
19
+ Register the provider and server hook:
20
+
21
+ ```typescript
22
+ import {
23
+ createDevtoolsHooks,
24
+ createDevtoolsProvider,
25
+ } from "@beignet/devtools";
26
+ import { createNextServer } from "@beignet/next";
27
+
28
+ export const server = await createNextServer({
29
+ ports,
30
+ providers: [createDevtoolsProvider(), ...providers],
31
+ hooks: [createDevtoolsHooks()],
32
+ createContext: ({ req, ports }) => ({
33
+ requestId: req.headers.get("x-request-id") ?? crypto.randomUUID(),
34
+ ports,
35
+ }),
36
+ });
37
+ ```
38
+
39
+ Add a catch-all route:
40
+
41
+ ```typescript
42
+ // app/api/devtools/[[...path]]/route.ts
43
+ import { createDevtoolsRoute } from "@beignet/devtools";
44
+ import { server } from "@/server";
45
+
46
+ export const { GET, POST } = createDevtoolsRoute(server.ports.devtools, {
47
+ basePath: "/api/devtools",
48
+ });
49
+ ```
50
+
51
+ Open `/api/devtools`.
52
+
53
+ The dashboard connects to the event stream with Server-Sent Events and falls
54
+ back to polling when `EventSource` is unavailable. It includes tabs for the
55
+ timeline, requests, use cases, errors, domain events, jobs, schedules, providers, and
56
+ provider-owned events such as database/cache/storage/core/mail/auth/audit/rate limit
57
+ activity, and custom events.
58
+ Request rows expand into correlated events that share the same `traceId` or
59
+ `requestId`.
60
+
61
+ Use the toolbar to search across event summaries, paths, messages, names,
62
+ watchers, IDs, and details. The dashboard also includes method, status, and
63
+ watcher filters for narrowing noisy timelines.
64
+
65
+ Provider-owned tabs render focused panels with subsystem metrics and rows. For
66
+ example, database shows query/provider context, cache shows hit and failure
67
+ counts, auth shows authenticated versus guest activity, audit shows durable
68
+ activity records, and rate limits show allowed versus blocked checks.
69
+
70
+ ## Local persistence
71
+
72
+ The default buffer is in memory. Enable local persistence when you want the
73
+ timeline to survive dev server restarts:
74
+
75
+ ```typescript
76
+ import {
77
+ createDevtoolsProvider,
78
+ createFileDevtoolsStore,
79
+ } from "@beignet/devtools";
80
+
81
+ createDevtoolsProvider({
82
+ store: createFileDevtoolsStore({
83
+ filePath: ".beignet/devtools/core/events.jsonl",
84
+ }),
85
+ });
86
+ ```
87
+
88
+ You can also enable the built-in file store through environment variables:
89
+
90
+ ```bash
91
+ DEVTOOLS_PERSIST=true
92
+ DEVTOOLS_PERSIST_PATH=.beignet/devtools/core/events.jsonl
93
+ ```
94
+
95
+ The file store writes JSONL and compacts to the most recent configured events.
96
+ `POST /api/devtools/clear` clears the in-memory buffer and the configured store.
97
+
98
+ ## Trace context
99
+
100
+ Devtools is OpenTelemetry-compatible without depending on the OpenTelemetry SDK.
101
+ `createDevtoolsHooks()` reads incoming W3C `traceparent` headers, creates a
102
+ local span when one is missing, exposes the current `traceparent` response
103
+ header, and adds trace fields to captured events.
104
+
105
+ All captured events can include:
106
+
107
+ - `traceId`: W3C trace ID for distributed correlation
108
+ - `spanId`: span ID for the operation represented by the event
109
+ - `parentSpanId`: parent span ID when the event is nested
110
+ - `traceparent`: W3C header value for the current span
111
+
112
+ For object contexts, the hook also adds these fields before the handler runs so
113
+ use case instrumentation can attach nested spans to the request trace.
114
+
115
+ ## Watchers
116
+
117
+ Devtools is organized around watchers. A watcher owns one category of capture
118
+ and records typed events into the shared timeline.
119
+
120
+ Built-in watchers:
121
+
122
+ - `requests` records HTTP request timing and contract route activity.
123
+ - `errors` records unhandled errors, use case failures, and devtools failures.
124
+ - `useCases` records application command and query execution.
125
+ - `eventBus` records domain event publishing.
126
+ - `jobs` records background job lifecycle events.
127
+ - `schedules` records scheduled task execution.
128
+ - `providers` records provider setup, start, and stop activity.
129
+ - `db` records database diagnostics from first-party providers.
130
+ - `cache` records cache diagnostics from first-party providers.
131
+ - `storage` records storage diagnostics from first-party providers.
132
+ - `mail` records mail diagnostics from first-party providers.
133
+ - `auth` records auth diagnostics from first-party providers.
134
+ - `audit` records sanitized durable audit activity emitted by application code.
135
+ - `rateLimit` records rate limit diagnostics from first-party providers.
136
+ - `custom` records application and integration-specific diagnostic events.
137
+
138
+ Configure watchers through the provider:
139
+
140
+ ```typescript
141
+ createDevtoolsProvider({
142
+ watchers: {
143
+ requests: true,
144
+ useCases: true,
145
+ eventBus: false,
146
+ jobs: false,
147
+ schedules: true,
148
+ db: true,
149
+ },
150
+ });
151
+ ```
152
+
153
+ Disabled watchers do not store matching events. The installed watcher metadata
154
+ is available through `ctx.ports.devtools.getWatchers()` and the dashboard API.
155
+
156
+ Custom integrations can also register watcher metadata for their own event
157
+ types. Custom watcher tabs appear in the dashboard when they own `custom`
158
+ events:
159
+
160
+ ```typescript
161
+ createDevtoolsProvider({
162
+ watchers: {
163
+ search: {
164
+ label: "Search",
165
+ description: "Search query and indexing diagnostics.",
166
+ eventTypes: ["custom"],
167
+ },
168
+ },
169
+ });
170
+ ```
171
+
172
+ Then record events with `watcher: "search"` so the custom watcher controls
173
+ whether they are stored.
174
+
175
+ ## Use case instrumentation
176
+
177
+ Bridge the application package's `onRun` hook once in your shared use case
178
+ factory:
179
+
180
+ ```typescript
181
+ import { createUseCase } from "@beignet/core/application";
182
+ import { createDevtoolsUseCaseObserver } from "@beignet/devtools";
183
+
184
+ export const useCase = createUseCase<AppContext>({
185
+ onRun: createDevtoolsUseCaseObserver<AppContext>(),
186
+ });
187
+ ```
188
+
189
+ The observer reads `ctx.ports.devtools`, `ctx.requestId`, and trace context
190
+ fields by default. Use case `start`, `end`, and `error` phases share the same
191
+ span when they run with the same request context.
192
+
193
+ ## Provider instrumentation
194
+
195
+ First-party and app-level providers should use
196
+ `createProviderInstrumentation()` from `@beignet/core/providers` instead of
197
+ depending on devtools directly. The helper accepts either a ports object or an
198
+ instrumentation port, records through `record()`, and adds provider metadata to
199
+ custom events. `@beignet/devtools` implements that instrumentation port
200
+ when `createDevtoolsProvider()` is registered.
201
+
202
+ ```typescript
203
+ import {
204
+ createProvider,
205
+ createProviderInstrumentation,
206
+ } from "@beignet/core/providers";
207
+
208
+ export const searchProvider = createProvider({
209
+ name: "search",
210
+ setup({ ports }) {
211
+ const instrumentation = createProviderInstrumentation(ports, {
212
+ providerName: "search",
213
+ watcher: "custom",
214
+ });
215
+
216
+ return {
217
+ ports: {
218
+ search: {
219
+ async query(text: string) {
220
+ const results = await runSearch(text);
221
+
222
+ instrumentation.custom({
223
+ name: "search.query",
224
+ label: "Search query",
225
+ summary: `${results.length} results`,
226
+ details: { resultCount: results.length },
227
+ });
228
+
229
+ return results;
230
+ },
231
+ },
232
+ },
233
+ };
234
+ },
235
+ });
236
+ ```
237
+
238
+ Use a built-in watcher such as `db`, `cache`, `storage`, `mail`, `auth`,
239
+ `audit`, `rateLimit`, or `schedules` when the provider belongs to one of those
240
+ categories. Use `custom` or a custom watcher name for application-specific
241
+ integrations.
242
+
243
+ ## Audit activity
244
+
245
+ Durable audit logs should still be written through your app's `AuditLogPort`.
246
+ Use `createDevtoolsAuditLog()` when you also want sanitized audit activity in
247
+ the local devtools timeline:
248
+
249
+ ```typescript
250
+ import { createDevtoolsAuditLog } from "@beignet/devtools";
251
+
252
+ const audit = createDevtoolsAuditLog({
253
+ audit: durableAudit,
254
+ devtools: ports.devtools,
255
+ });
256
+ ```
257
+
258
+ The wrapper records the durable audit entry first, then emits a custom devtools
259
+ event owned by the `audit` watcher. Devtools remains a local diagnostic view;
260
+ it is not the durable audit store.
261
+
262
+ When an audit port is transaction-scoped, emit the devtools mirror only after
263
+ the transaction commits. Keeping the transaction-scoped audit port durable-only
264
+ is preferable to showing a local audit event for work that later rolls back.
265
+
266
+ ## Manual events
267
+
268
+ Use `record()` for application-specific events. It fills `id` and `timestamp`.
269
+
270
+ ```typescript
271
+ ctx.ports.devtools.record({
272
+ type: "custom",
273
+ watcher: "search",
274
+ name: "search.query",
275
+ label: "Search query",
276
+ summary: "24 results in 18ms",
277
+ details: {
278
+ query,
279
+ resultCount: 24,
280
+ durationMs: 18,
281
+ },
282
+ });
283
+ ```
284
+
285
+ `log()` is still available when you already have a complete `DevtoolsEvent`.
286
+
287
+ ## Endpoints
288
+
289
+ - `GET /api/devtools` serves the dashboard
290
+ - `GET /api/devtools/core/events` returns JSON events
291
+ - `GET /api/devtools/stream` returns a live Server-Sent Events stream
292
+ - `POST /api/devtools/clear` clears the in-memory buffer and configured store
293
+
294
+ Event list query parameters:
295
+
296
+ - `type`: `request`, `error`, `usecase`, `eventBus`, `job`, `schedule`, `provider`, or `custom`
297
+ - `requestId`: correlation ID
298
+ - `traceId`: W3C trace ID
299
+ - `limit`: maximum events to return, default `200`
300
+
301
+ ## Configuration
302
+
303
+ ```bash
304
+ DEVTOOLS_ENABLED=true
305
+ DEVTOOLS_ENABLED=false
306
+ DEVTOOLS_MAX_EVENTS=1000
307
+ DEVTOOLS_PERSIST=true
308
+ DEVTOOLS_PERSIST_PATH=.beignet/devtools/core/events.jsonl
309
+ ```
310
+
311
+ The default buffer keeps the latest 500 events. Persistence is opt-in and uses
312
+ `.beignet/devtools/core/events.jsonl` by default when enabled without a custom
313
+ path.
314
+
315
+ The provider controls whether events are recorded. The HTTP route controls
316
+ whether those events are exposed. Both default to development-only behavior.
317
+ Route handlers return `404` when `NODE_ENV === "production"` unless explicitly
318
+ enabled:
319
+
320
+ ```typescript
321
+ export const { GET, POST } = createDevtoolsRoute(server.ports.devtools, {
322
+ basePath: "/api/devtools",
323
+ enabled: process.env.DEVTOOLS_ENABLED === "true",
324
+ authorize: (req: Request) =>
325
+ req.headers.get("x-devtools-token") === process.env.DEVTOOLS_TOKEN,
326
+ });
327
+ ```
328
+
329
+ If `authorize` returns `false`, devtools responds with `404`. If it returns a
330
+ `Response`, that response is used, which lets applications return their own
331
+ `403` or redirect response.
332
+
333
+ ## Redaction
334
+
335
+ Devtools uses the shared redaction helpers from `@beignet/core/ports` before
336
+ events are stored. Sensitive keys such as `authorization`, `cookie`,
337
+ `set-cookie`, `x-api-key`, `token`, `password`, `secret`, and `credentials` are
338
+ replaced with `[redacted]`.
339
+
340
+ Request hooks record request headers for debugging, but do not record request or
341
+ response bodies by default.
342
+
343
+ You can add a custom redactor:
344
+
345
+ ```typescript
346
+ createDevtoolsHooks({
347
+ redact: (event) => ({
348
+ ...event,
349
+ details: scrub(event.details),
350
+ }),
351
+ });
352
+ ```
353
+
354
+ The in-memory store also accepts a redactor for custom setups:
355
+
356
+ ```typescript
357
+ const devtools = createInMemoryDevtools({
358
+ redact: (event) => event,
359
+ });
360
+ ```
361
+
362
+ ## API
363
+
364
+ ```typescript
365
+ interface DevtoolsPort {
366
+ log(event: DevtoolsEvent): void;
367
+ record(event: DevtoolsEventInput): DevtoolsEvent;
368
+ subscribe(listener: DevtoolsListener): () => void;
369
+ getEvents(filter?: DevtoolsFilter): DevtoolsEvent[];
370
+ getWatchers(): DevtoolsWatcher[];
371
+ isWatcherEnabled(name: DevtoolsWatcherName): boolean;
372
+ clear(): void | Promise<void>;
373
+ }
374
+ ```
375
+
376
+ ```typescript
377
+ function createFileDevtoolsStore(options?: {
378
+ filePath?: string;
379
+ maxEvents?: number;
380
+ compactEvery?: number;
381
+ }): DevtoolsEventStore;
382
+ ```
383
+
384
+ ```typescript
385
+ function createProviderInstrumentation(
386
+ target: ProviderInstrumentationTarget,
387
+ options: {
388
+ providerName: string;
389
+ watcher?: string;
390
+ redact?: (event: ProviderInstrumentationEventInput) => ProviderInstrumentationEventInput;
391
+ },
392
+ ): ProviderInstrumentation;
393
+ ```
394
+
395
+ ```typescript
396
+ function createDevtoolsAuditLog(options: {
397
+ audit: AuditLogPort;
398
+ devtools?: DevtoolsPort;
399
+ emit?: boolean;
400
+ redact?: (entry: AuditLogEntry) => AuditLogEntry;
401
+ }): AuditLogPort;
402
+ ```
403
+
404
+ ```typescript
405
+ type DevtoolsEvent =
406
+ | RequestEvent
407
+ | ErrorEvent
408
+ | UseCaseEvent
409
+ | EventBusEvent
410
+ | JobEvent
411
+ | ScheduleEvent
412
+ | ProviderEvent
413
+ | CustomDevtoolsEvent;
414
+ ```
415
+
416
+ All events include `id`, `timestamp`, optional `requestId`, optional `watcher`,
417
+ optional `traceId`, optional `spanId`, optional `parentSpanId`, optional
418
+ `traceparent`, and optional redacted `details`.
419
+
420
+ `createDevtoolsHooks()` accepts:
421
+
422
+ ```typescript
423
+ type DevtoolsHooksOptions<Ctx> = {
424
+ basePath?: string;
425
+ requestIdHeader?: string | false;
426
+ traceContextHeader?: string | false;
427
+ getRequestId?: (args: {
428
+ req: HttpRequestLike;
429
+ ctx?: Ctx;
430
+ response?: HttpResponseLike;
431
+ }) => string | undefined;
432
+ getTraceContext?: (args: {
433
+ req: HttpRequestLike;
434
+ ctx?: Ctx;
435
+ response?: HttpResponseLike;
436
+ }) => DevtoolsTraceContextInput | string | undefined;
437
+ redact?: DevtoolsRedactor;
438
+ };
439
+ ```
440
+
441
+ `createDevtoolsRoute()` and `handleDevtoolsRequest()` accept:
442
+
443
+ ```typescript
444
+ type DevtoolsRequestOptions = {
445
+ basePath: string;
446
+ enabled?: boolean;
447
+ authorize?: (
448
+ req: Request,
449
+ ) => boolean | Response | Promise<boolean | Response>;
450
+ };
451
+ ```
452
+
453
+ ## Production safety
454
+
455
+ The HTTP handlers return `404` when `NODE_ENV === "production"` by default. The
456
+ provider also installs a no-op devtools port in production by default so app
457
+ code does not need null checks.
458
+
459
+ Devtools can contain sensitive request, error, and domain data. Keep it on local
460
+ development routes unless you intentionally add authentication and redaction.
461
+
462
+ ## License
463
+
464
+ MIT
@@ -0,0 +1,21 @@
1
+ export type DevtoolsAuthorizeResult = boolean | Response;
2
+ export type DevtoolsAuthorize = (req: Request) => DevtoolsAuthorizeResult | Promise<DevtoolsAuthorizeResult>;
3
+ export interface DevtoolsRouteAccessOptions {
4
+ /**
5
+ * Whether the devtools route should be exposed.
6
+ *
7
+ * @default process.env.NODE_ENV !== "production"
8
+ */
9
+ enabled?: boolean;
10
+ /**
11
+ * Optional app-owned authorization check for exposed devtools routes.
12
+ *
13
+ * Returning `false` hides devtools with a 404. Returning a `Response` lets
14
+ * the app provide a custom denial response.
15
+ */
16
+ authorize?: DevtoolsAuthorize;
17
+ }
18
+ export declare function isDevtoolsRouteEnabled(options?: DevtoolsRouteAccessOptions): boolean;
19
+ export declare function devtoolsNotFoundResponse(): Response;
20
+ export declare function authorizeDevtoolsRequest(req: Request, options?: DevtoolsRouteAccessOptions): Promise<Response | undefined>;
21
+ //# sourceMappingURL=access.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"access.d.ts","sourceRoot":"","sources":["../src/access.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,uBAAuB,GAAG,OAAO,GAAG,QAAQ,CAAC;AAEzD,MAAM,MAAM,iBAAiB,GAAG,CAC9B,GAAG,EAAE,OAAO,KACT,uBAAuB,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAAC;AAEhE,MAAM,WAAW,0BAA0B;IACzC;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,iBAAiB,CAAC;CAC/B;AAED,wBAAgB,sBAAsB,CACpC,OAAO,GAAE,0BAA+B,GACvC,OAAO,CAET;AAED,wBAAgB,wBAAwB,IAAI,QAAQ,CAEnD;AAED,wBAAsB,wBAAwB,CAC5C,GAAG,EAAE,OAAO,EACZ,OAAO,GAAE,0BAA+B,GACvC,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,CAe/B"}
package/dist/access.js ADDED
@@ -0,0 +1,20 @@
1
+ export function isDevtoolsRouteEnabled(options = {}) {
2
+ return options.enabled ?? process.env.NODE_ENV !== "production";
3
+ }
4
+ export function devtoolsNotFoundResponse() {
5
+ return new Response("Not Found", { status: 404 });
6
+ }
7
+ export async function authorizeDevtoolsRequest(req, options = {}) {
8
+ if (!isDevtoolsRouteEnabled(options)) {
9
+ return devtoolsNotFoundResponse();
10
+ }
11
+ if (!options.authorize) {
12
+ return undefined;
13
+ }
14
+ const result = await options.authorize(req);
15
+ if (result instanceof Response) {
16
+ return result;
17
+ }
18
+ return result ? undefined : devtoolsNotFoundResponse();
19
+ }
20
+ //# sourceMappingURL=access.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"access.js","sourceRoot":"","sources":["../src/access.ts"],"names":[],"mappings":"AAuBA,MAAM,UAAU,sBAAsB,CACpC,UAAsC,EAAE;IAExC,OAAO,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,CAAC;AAClE,CAAC;AAED,MAAM,UAAU,wBAAwB;IACtC,OAAO,IAAI,QAAQ,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;AACpD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC5C,GAAY,EACZ,UAAsC,EAAE;IAExC,IAAI,CAAC,sBAAsB,CAAC,OAAO,CAAC,EAAE,CAAC;QACrC,OAAO,wBAAwB,EAAE,CAAC;IACpC,CAAC;IAED,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC;QACvB,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IAC5C,IAAI,MAAM,YAAY,QAAQ,EAAE,CAAC;QAC/B,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,OAAO,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,wBAAwB,EAAE,CAAC;AACzD,CAAC"}
@@ -0,0 +1,10 @@
1
+ import { type AuditLogEntry, type AuditLogPort } from "@beignet/core/ports";
2
+ import type { DevtoolsPort } from "./index";
3
+ export interface DevtoolsAuditLogOptions {
4
+ audit: AuditLogPort;
5
+ devtools?: DevtoolsPort;
6
+ emit?: boolean;
7
+ redact?: (entry: AuditLogEntry) => AuditLogEntry;
8
+ }
9
+ export declare function createDevtoolsAuditLog(options: DevtoolsAuditLogOptions): AuditLogPort;
10
+ //# sourceMappingURL=audit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,aAAa,EAElB,KAAK,YAAY,EAGlB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE5C,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,YAAY,CAAC;IACpB,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,aAAa,CAAC;CAClD;AAoBD,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,uBAAuB,GAC/B,YAAY,CAkCd"}
package/dist/audit.js ADDED
@@ -0,0 +1,49 @@
1
+ import { normalizeAuditLogEntry, redactAuditLogEntry, } from "@beignet/core/ports";
2
+ function prepareEntry(input, redact) {
3
+ const redacted = redactAuditLogEntry(normalizeAuditLogEntry(input));
4
+ return redact ? redact(redacted) : redacted;
5
+ }
6
+ function auditSummary(entry) {
7
+ const resource = entry.resource?.id
8
+ ? `${entry.resource.type}:${entry.resource.id}`
9
+ : entry.resource?.type;
10
+ const outcome = entry.outcome === "failure" ? "failed" : "succeeded";
11
+ return resource
12
+ ? `${entry.action} ${outcome} for ${resource}`
13
+ : `${entry.action} ${outcome}`;
14
+ }
15
+ export function createDevtoolsAuditLog(options) {
16
+ return {
17
+ async record(input) {
18
+ const entry = prepareEntry(input, options.redact);
19
+ await options.audit.record(entry);
20
+ if (options.emit === false || !options.devtools)
21
+ return;
22
+ try {
23
+ options.devtools.record({
24
+ type: "custom",
25
+ watcher: "audit",
26
+ name: entry.action,
27
+ label: "Audit",
28
+ summary: auditSummary(entry),
29
+ requestId: entry.requestId,
30
+ traceId: entry.traceId,
31
+ details: {
32
+ action: entry.action,
33
+ actor: entry.actor,
34
+ tenant: entry.tenant,
35
+ resource: entry.resource,
36
+ outcome: entry.outcome,
37
+ message: entry.message,
38
+ metadata: entry.metadata,
39
+ occurredAt: entry.occurredAt.toISOString(),
40
+ },
41
+ });
42
+ }
43
+ catch {
44
+ // Devtools is an observer; durable audit writes must not depend on it.
45
+ }
46
+ },
47
+ };
48
+ }
49
+ //# sourceMappingURL=audit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.js","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,sBAAsB,EACtB,mBAAmB,GACpB,MAAM,qBAAqB,CAAC;AAU7B,SAAS,YAAY,CACnB,KAAyB,EACzB,MAAgD;IAEhD,MAAM,QAAQ,GAAG,mBAAmB,CAAC,sBAAsB,CAAC,KAAK,CAAC,CAAC,CAAC;IACpE,OAAO,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;AAC9C,CAAC;AAED,SAAS,YAAY,CAAC,KAAoB;IACxC,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,EAAE,EAAE;QACjC,CAAC,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,IAAI,IAAI,KAAK,CAAC,QAAQ,CAAC,EAAE,EAAE;QAC/C,CAAC,CAAC,KAAK,CAAC,QAAQ,EAAE,IAAI,CAAC;IACzB,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC;IACrE,OAAO,QAAQ;QACb,CAAC,CAAC,GAAG,KAAK,CAAC,MAAM,IAAI,OAAO,QAAQ,QAAQ,EAAE;QAC9C,CAAC,CAAC,GAAG,KAAK,CAAC,MAAM,IAAI,OAAO,EAAE,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,sBAAsB,CACpC,OAAgC;IAEhC,OAAO;QACL,KAAK,CAAC,MAAM,CAAC,KAAK;YAChB,MAAM,KAAK,GAAG,YAAY,CAAC,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;YAElD,MAAM,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAElC,IAAI,OAAO,CAAC,IAAI,KAAK,KAAK,IAAI,CAAC,OAAO,CAAC,QAAQ;gBAAE,OAAO;YAExD,IAAI,CAAC;gBACH,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;oBACtB,IAAI,EAAE,QAAQ;oBACd,OAAO,EAAE,OAAO;oBAChB,IAAI,EAAE,KAAK,CAAC,MAAM;oBAClB,KAAK,EAAE,OAAO;oBACd,OAAO,EAAE,YAAY,CAAC,KAAK,CAAC;oBAC5B,SAAS,EAAE,KAAK,CAAC,SAAS;oBAC1B,OAAO,EAAE,KAAK,CAAC,OAAO;oBACtB,OAAO,EAAE;wBACP,MAAM,EAAE,KAAK,CAAC,MAAM;wBACpB,KAAK,EAAE,KAAK,CAAC,KAAK;wBAClB,MAAM,EAAE,KAAK,CAAC,MAAM;wBACpB,QAAQ,EAAE,KAAK,CAAC,QAAQ;wBACxB,OAAO,EAAE,KAAK,CAAC,OAAO;wBACtB,OAAO,EAAE,KAAK,CAAC,OAAO;wBACtB,QAAQ,EAAE,KAAK,CAAC,QAAQ;wBACxB,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,WAAW,EAAE;qBAC3C;iBACF,CAAC,CAAC;YACL,CAAC;YAAC,MAAM,CAAC;gBACP,uEAAuE;YACzE,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}