@bazariodev/docket 0.0.0-stage → 0.0.0

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 (88) hide show
  1. package/CHANGELOG.md +1 -0
  2. package/LICENSE +21 -0
  3. package/README.md +443 -2
  4. package/dist/browser/constants.d.cts +14 -0
  5. package/dist/browser/constants.d.ts +14 -0
  6. package/dist/browser/index.cjs +192 -0
  7. package/dist/browser/index.cjs.map +1 -0
  8. package/dist/browser/index.d.cts +29 -0
  9. package/dist/browser/index.d.ts +29 -0
  10. package/dist/browser/index.js +187 -0
  11. package/dist/browser/index.js.map +1 -0
  12. package/dist/chunk-7ld72rnl.js +376 -0
  13. package/dist/chunk-7ld72rnl.js.map +1 -0
  14. package/dist/chunk-B-SP8Od_.js +437 -0
  15. package/dist/chunk-B-SP8Od_.js.map +1 -0
  16. package/dist/chunk-BLf-eCDO.cjs +53 -0
  17. package/dist/chunk-BLf-eCDO.cjs.map +1 -0
  18. package/dist/chunk-Bvq4tF_F.cjs +440 -0
  19. package/dist/chunk-Bvq4tF_F.cjs.map +1 -0
  20. package/dist/chunk-CEhpwtwJ.cjs +379 -0
  21. package/dist/chunk-CEhpwtwJ.cjs.map +1 -0
  22. package/dist/chunk-CSS3f4_M.js +46 -0
  23. package/dist/chunk-CSS3f4_M.js.map +1 -0
  24. package/dist/chunk-CXZYNALm.cjs +124 -0
  25. package/dist/chunk-CXZYNALm.cjs.map +1 -0
  26. package/dist/chunk-DYcNnkOV.js +50 -0
  27. package/dist/chunk-DYcNnkOV.js.map +1 -0
  28. package/dist/chunk-DqK_3eFr.cjs +53 -0
  29. package/dist/chunk-DqK_3eFr.cjs.map +1 -0
  30. package/dist/chunk-LRCHwVuE.js +110 -0
  31. package/dist/chunk-LRCHwVuE.js.map +1 -0
  32. package/dist/constants.d.cts +6 -0
  33. package/dist/constants.d.ts +6 -0
  34. package/dist/core/constants.d.cts +43 -0
  35. package/dist/core/constants.d.ts +43 -0
  36. package/dist/core/errors.d.cts +8 -0
  37. package/dist/core/errors.d.ts +8 -0
  38. package/dist/devtools/index.cjs +37 -0
  39. package/dist/devtools/index.cjs.map +1 -0
  40. package/dist/devtools/index.d.cts +11 -0
  41. package/dist/devtools/index.d.ts +11 -0
  42. package/dist/devtools/index.js +35 -0
  43. package/dist/devtools/index.js.map +1 -0
  44. package/dist/export/index.cjs +111 -0
  45. package/dist/export/index.cjs.map +1 -0
  46. package/dist/export/index.d.cts +49 -0
  47. package/dist/export/index.d.ts +49 -0
  48. package/dist/export/index.js +109 -0
  49. package/dist/export/index.js.map +1 -0
  50. package/dist/history/constants.d.cts +35 -0
  51. package/dist/history/constants.d.ts +35 -0
  52. package/dist/history/index.cjs +1196 -0
  53. package/dist/history/index.cjs.map +1 -0
  54. package/dist/history/index.d.cts +113 -0
  55. package/dist/history/index.d.ts +113 -0
  56. package/dist/history/index.js +1194 -0
  57. package/dist/history/index.js.map +1 -0
  58. package/dist/index.cjs +14 -0
  59. package/dist/index.cjs.map +1 -0
  60. package/dist/index.d.cts +158 -0
  61. package/dist/index.d.ts +158 -0
  62. package/dist/index.js +6 -0
  63. package/dist/index.js.map +1 -0
  64. package/dist/indexeddb/index.cjs +15 -0
  65. package/dist/indexeddb/index.cjs.map +1 -0
  66. package/dist/indexeddb/index.d.cts +46 -0
  67. package/dist/indexeddb/index.d.ts +46 -0
  68. package/dist/indexeddb/index.js +13 -0
  69. package/dist/indexeddb/index.js.map +1 -0
  70. package/dist/redaction/index.cjs +154 -0
  71. package/dist/redaction/index.cjs.map +1 -0
  72. package/dist/redaction/index.d.cts +70 -0
  73. package/dist/redaction/index.d.ts +70 -0
  74. package/dist/redaction/index.js +152 -0
  75. package/dist/redaction/index.js.map +1 -0
  76. package/dist/sip/constants.d.cts +5 -0
  77. package/dist/sip/constants.d.ts +5 -0
  78. package/dist/sip/index.cjs +336 -0
  79. package/dist/sip/index.cjs.map +1 -0
  80. package/dist/sip/index.d.cts +34 -0
  81. package/dist/sip/index.d.ts +34 -0
  82. package/dist/sip/index.js +333 -0
  83. package/dist/sip/index.js.map +1 -0
  84. package/dist/storage/constants.d.cts +11 -0
  85. package/dist/storage/constants.d.ts +11 -0
  86. package/dist/storage/contracts.d.cts +35 -0
  87. package/dist/storage/contracts.d.ts +35 -0
  88. package/package.json +127 -3
package/CHANGELOG.md ADDED
@@ -0,0 +1 @@
1
+ # Changelog
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Bazario
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,444 @@
1
- # Temporary Holding Version
1
+ # @bazariodev/docket
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Record structured diagnostic data. Remove private values before delivery, keep bounded history,
4
+ and build ZIP files for download or application upload.
5
+
6
+ ## Install
7
+
8
+ ```sh
9
+ pnpm add @bazariodev/docket
10
+ ```
11
+
12
+ Docket has no runtime dependencies, optional dependencies, or peer dependencies.
13
+ Its runtime files and declarations do not import provider packages. The application selects
14
+ providers and supplies their capabilities. History, conversion, persistence, SIP, and ZIP support
15
+ are optional. Import only the entries that the application uses.
16
+
17
+ The usage example selects [Winnow](https://www.npmjs.com/package/@bazariodev/winnow) for bounded
18
+ conversion and [Hopper](https://www.npmjs.com/package/@bazariodev/hopper) for memory storage:
19
+
20
+ ```sh
21
+ pnpm add @bazariodev/winnow @bazariodev/hopper
22
+ ```
23
+
24
+ Other providers can implement the same structural ports. A selected provider can install its own
25
+ dependencies. Docket does not change that provider's package graph. Optional examples below also
26
+ select Baler for ZIP files and Baton for application ownership.
27
+ Install these packages only for the corresponding application composition.
28
+
29
+ Node.js support is `^22.12.0 || ^24.0.0 || >=26.0.0`. Browser persistence needs the selected storage
30
+ provider's platform APIs. The Hopper IndexedDB composition uses IndexedDB and Web Locks.
31
+ Managed history uses Web Crypto and defaults to localStorage for its metadata.
32
+
33
+ ## Usage
34
+
35
+ ```js
36
+ import { createLogging } from '@bazariodev/docket';
37
+ import { createRedactor } from '@bazariodev/docket/redaction';
38
+ import { createMemoryStore } from '@bazariodev/hopper';
39
+ import * as conversion from '@bazariodev/winnow';
40
+ const redactor = createRedactor(
41
+ { fields: { password: 'drop', accessToken: 'drop' } },
42
+ conversion,
43
+ );
44
+ const store = createMemoryStore();
45
+ const logging = createLogging({
46
+ areas: [{ id: 'application' }, { id: 'network', level: 'warn' }],
47
+ redactor,
48
+ sinks: [store],
49
+ });
50
+ const log = logging.getLogger('application', 'Connection');
51
+ log.info('Connection ready', { state: 'online' });
52
+ ```
53
+
54
+ The later examples reuse `conversion`, `redactor`, and `store` from this example.
55
+ Supply your application's area list and privacy policy. The default text rules are a starting
56
+ point. They do not identify every private field in an application.
57
+
58
+ ## Consumer redaction policy
59
+
60
+ The application selects which values to keep, remove, mask, or hash. Redaction is the complete
61
+ process; masking and hashing are two possible actions. The package does not require hashing for
62
+ core logging, SIP, storage, or export.
63
+
64
+ ```js
65
+ import { createRedactor } from '@bazariodev/docket/redaction';
66
+
67
+ const redactor = createRedactor({
68
+ fields: { password: 'drop', email: 'mask', state: 'keep' },
69
+ unknownFields: 'drop',
70
+ }, conversion);
71
+ ```
72
+
73
+ Field names match without case at every data depth, including context and Error properties.
74
+
75
+ | Action | Result |
76
+ | --- | --- |
77
+ | `keep` | Keep the field and apply nested and text rules. This does not bypass text redaction. |
78
+ | `drop` | Remove the field. |
79
+ | `mask` | Hide part of the value. Short strings are hidden completely. Longer digit strings keep the last four digits; other strings keep the first and last character. |
80
+ | `hash` | Replace the value with the result of the application's hash function. |
81
+
82
+ Use `text` rules to replace selected text with a fixed value such as `[redacted]`, or to apply
83
+ another application rule. Custom `policy.text` rules receive the Docket record API. An explicit
84
+ `text` list replaces the default list. `text: []` disables text rules. Provider defaults belong in
85
+ `conversion.TEXT_RULES`; their callbacks receive the value engine API. To select a provider subset,
86
+ supply a dictionary such as `{ email: TEXT_RULES.email }` as `TEXT_RULES` and omit `policy.text`.
87
+ Unknown fields default to `keep`; use `unknownFields: 'drop'` when only named fields are permitted.
88
+
89
+ Field mask/hash actions convert numbers and bigint to text. Other non-string values use a fixed
90
+ redaction marker without a hash call. Error schema field rules use the same actions. Captured runtime
91
+ accessors supply Error stacks and DOMException fields. Runtime stack formatting can call an
92
+ application `Error.prepareStackTrace` function.
93
+
94
+ Hashing is optional. The application supplies the function and owns its algorithm and key.
95
+ For example, the application can call this factory with its synchronous keyed hasher:
96
+
97
+ ```js
98
+ import { createRedactor } from '@bazariodev/docket/redaction';
99
+
100
+ export function createApplicationRedactor(hashPrivateValue, conversion) {
101
+ return createRedactor({
102
+ hash: hashPrivateValue,
103
+ fields: { customerId: 'hash', password: 'drop' },
104
+ unknownFields: 'drop',
105
+ }, conversion);
106
+ }
107
+ ```
108
+
109
+ Text-rule methods use the compiled rule as their receiver and receive the Docket redactor API.
110
+ Docket captures the hash function at construction and calls it with the original policy as its receiver.
111
+ Prototype hash methods are supported. Later changes to `policy.hash` do not replace the captured
112
+ function; the captured method can still read current policy state.
113
+
114
+ The hash function must return a string synchronously. A Promise is not supported. A hash rule
115
+ without a function rejects policy creation. The package checks the output type and limits; it
116
+ does not verify the algorithm's security. Keep secret keys out of records and exports. Keyed
117
+ tokens can connect equal values under the same key; they do not encrypt the logs.
118
+
119
+ An application can also supply its own `Redactor`. The hub still validates, copies, and freezes
120
+ its output within the declared limits. These checks do not prove that a privacy policy removes
121
+ all private data. Select the policy before recording. Export cannot restore removed values.
122
+
123
+ Managed history's account-owner digest is separate from record redaction. It uses native Web
124
+ Crypto. A history redactor can ignore the supplied history key when its policy does not use it.
125
+
126
+ ## SIP display names
127
+
128
+ ```js
129
+ import { createRedactor } from '@bazariodev/docket/redaction';
130
+ import { createSipTools } from '@bazariodev/docket/sip';
131
+ import { TEXT_RULES } from '@bazariodev/winnow';
132
+
133
+ const { redactSipMessage } = createSipTools(TEXT_RULES);
134
+ const redactor = createRedactor({}, conversion);
135
+ const message = 'SIP/2.0 200 OK\r\nFrom: "Alice" <sip:alice@example.com>\r\n\r\n';
136
+ const fixed = redactSipMessage(message, { redactor });
137
+ const masked = redactSipMessage(message, {
138
+ redactor,
139
+ redactDisplayName: (value) => redactor.mask(value),
140
+ });
141
+ ```
142
+
143
+ The SIP factory needs five match-only rules: `ipv4`, `ipv6`, `urlCredentials`, `sipUri`, and
144
+ `telUri`. Winnow supplies these rules directly. Other providers must supply replacements that
145
+ use only the matched text. The helper applies each address rule once. The returned SDP rules
146
+ belong only in a SIP-specific policy. Do not add them to the general provider defaults.
147
+
148
+ Without a callback, each non-empty display name becomes `[redacted]`. The callback receives the
149
+ name once, without source quotes and with quoted escapes decoded. It returns plain text. The
150
+ helper escapes quotes and backslashes and writes a quoted name. An empty result removes the name.
151
+ An identity callback, `(value) => value`, keeps the name value. A consumer can pass its injected
152
+ hash function as `redactDisplayName`; the helper does not select `redactor.hash` automatically.
153
+ Other SIP, credential, address, and SDP rules still apply.
154
+
155
+ The callback must return a string synchronously, without control characters, within
156
+ `redactor.limits.maxStringLength`. Invalid results throw; callback errors propagate. Malformed
157
+ identity lists use a fixed replacement without calling the callback. There is no raw-input fallback.
158
+ The helper limits input and complete output to four times `maxStringLength`. Oversized input
159
+ becomes `[truncated]`. Output growth throws `RangeError`. It counts escaped callback output across
160
+ headers before assembly and stops at the limit. Custom callbacks are trusted application code.
161
+
162
+ ## Export and application upload
163
+
164
+ ```js
165
+ import { createLogBundle } from '@bazariodev/docket/export';
166
+ import { downloadBlob } from '@bazariodev/docket/browser';
167
+ import * as archive from '@bazariodev/baler';
168
+
169
+ const bundle = await createLogBundle({
170
+ source: store,
171
+ redactor,
172
+ files: [{ name: 'application.log', areas: ['application', 'network'] }],
173
+ }, archive);
174
+ downloadBlob(bundle.blob, bundle.fileName);
175
+ ```
176
+
177
+ The application owns upload. It supplies the endpoint, credentials, request format, response
178
+ checks, cancellation, and timeout. A manual upload does not clear history. The package has no
179
+ HTTP client, retry service, delivery acknowledgement, or durable upload queue.
180
+
181
+ ```ts
182
+ import type { LogRecord } from '@bazariodev/docket';
183
+ import { createMemoryStore } from '@bazariodev/hopper';
184
+ import * as archive from '@bazariodev/baler';
185
+ import { createLogBundle } from '@bazariodev/docket/export';
186
+
187
+ // The application supplies sendFile and reportError.
188
+ const store = createMemoryStore<LogRecord>({
189
+ overflowStrategy: 'rotate',
190
+ async onRotate(source) {
191
+ const file = await createLogBundle({
192
+ source,
193
+ redactor,
194
+ files: [{ name: 'application.log', areas: ['application'] }],
195
+ }, archive);
196
+ await sendFile(file);
197
+ },
198
+ onError: reportError,
199
+ });
200
+ ```
201
+
202
+ Here, `redactor` is the same policy used for recording. Rotation removes a retained batch before
203
+ it calls the application. Recording continues while that callback is pending. Further capacity
204
+ pressure uses oldest-record eviction until the callback settles. A failed upload reports
205
+ `rotation`; it does not restore records or retry. Clear and stop invalidate the snapshot without
206
+ waiting for networking. An upload that already started belongs to the application.
207
+
208
+ ## Persistent storage
209
+
210
+ ```js
211
+ import { createIndexedDbLogStore } from '@bazariodev/docket/indexeddb';
212
+ import { createIndexedDbStore } from '@bazariodev/hopper/indexeddb';
213
+
214
+ const store = createIndexedDbLogStore(
215
+ { databaseName: 'example-diagnostics' },
216
+ createIndexedDbStore,
217
+ );
218
+ // Attach the store to a logging hub before recording.
219
+ await store.flush();
220
+ const status = store.getStatus();
221
+ ```
222
+
223
+ The supplied Hopper store first tries an exclusive database lock. A blocked or unavailable lock selects memory
224
+ fallback for that instance's lifetime. It cannot later replay that fallback into another owner's
225
+ history. A new instance can try ownership again. With `allowUnprotected: true`, failed acquisition
226
+ permits IndexedDB access without a lease. The store still tries the lock first.
227
+
228
+ Unprotected access cannot prevent another writer from recording after clear. It cannot establish
229
+ stable account history. Read `unprotected` and `unavailableHistory` from status after an operation.
230
+ Status reads are synchronous; initial acquisition is asynchronous.
231
+
232
+ An open or write failure with permitted access starts bounded memory fallback. Recovery retries
233
+ every ten minutes. A failed write batch is lost and its flush rejects. Other pending records move
234
+ to fallback. Successful recovery removes copied fallback records only after commit.
235
+
236
+ ## Account history
237
+
238
+ Use `/history` when the package must own account cleanup, keys, and reload state. Acquire an
239
+ application lease first. Use a stable key for each application, environment, and tenant.
240
+
241
+ ```js
242
+ import { createBaton } from '@bazariodev/baton';
243
+ import { createRedactor } from '@bazariodev/docket/redaction';
244
+ import { createLogHistory } from '@bazariodev/docket/history';
245
+ import { createIndexedDbLogStore } from '@bazariodev/docket/indexeddb';
246
+ import { copyRecords, createMemoryStore, validateStoreOptions } from '@bazariodev/hopper';
247
+ import { createIndexedDbStore } from '@bazariodev/hopper/indexeddb';
248
+
249
+ let history;
250
+ const baton = createBaton({
251
+ key: JSON.stringify(['example-app', 'production', 'example-tenant']),
252
+ async beforeRelease() { await history?.stop(); },
253
+ });
254
+ const claim = await baton.take();
255
+ if (claim.state !== 'owner') throw new Error('Application ownership is unavailable');
256
+ history = createLogHistory({
257
+ lease: claim.lease,
258
+ logging: { areas: [{ id: 'application' }] },
259
+ createRedactor: () => createRedactor({ fields: { password: 'drop' } }, conversion),
260
+ stores: {
261
+ createMemoryStore,
262
+ createIndexedDbStore: (options) => createIndexedDbLogStore(options, createIndexedDbStore),
263
+ validateStoreOptions,
264
+ copyRecords,
265
+ },
266
+ });
267
+ const log = history.getLogger('application', 'Connection');
268
+ log.info('Application startup');
269
+ await history.setOwner('example-tenant:example-account');
270
+ log.info('Account ready');
271
+ ```
272
+
273
+ History owns each supplied store and its private serial queue. It stops each store.
274
+ The `stores` object groups the two factories, `validateStoreOptions`, and `copyRecords`.
275
+ Managed hubs create their own run IDs and admission guard. `logging.createRunId` and
276
+ `logging.isActive` are rejected. The helper must control record identity and session validity.
277
+ Forwarding child loggers capture scope and context when created. Later changes to the supplied
278
+ binding do not change that snapshot. Invalid bindings remain contained.
279
+ The persistent factory must support cleanup even in memory or off mode. A prior account can
280
+ have records on disk. Missing cleanup must not be reported as successful erasure. For standalone
281
+ memory logging without account-history coordination, use the root with a memory sink.
282
+
283
+ The helper generates history keys internally. The redactor factory can ignore the key, or pass
284
+ it to an application-selected synchronous hasher. Use `resume(ownerId)` after reload with a verified
285
+ current account. Use `setOwner(ownerId)` for explicit sign-in or account replacement.
286
+
287
+ | Application event | Helper call | Application work |
288
+ | --- | --- | --- |
289
+ | Startup after lease acquisition | Create history; keep `history.getLogger(area, scope)` | Supply recording policy before producers start. |
290
+ | Export before sign-in | `getSession()`; use its source and redactor | Startup recording needs no account ID. |
291
+ | Reload with a verified account | `resume(ownerId)` | Supply the current authenticated identity. |
292
+ | Explicit sign-in or account replacement | `setOwner(ownerId)` | Wait for authentication first. |
293
+ | Manual logout | `logout()` | Show cleanup failure; do not claim erasure until success. |
294
+ | Forced authentication failure | Keep the helper | Retain history and key; stop only if recording must stop. |
295
+ | Clear history | `clear()` | Refresh status in the view. |
296
+ | Recording or persistence setting | `setMode(mode)` | Supply the permitted mode. |
297
+ | Failed deletion | `retryCleanup()` | Show the resulting status. |
298
+ | Environment or tenant change | Stop the old baton; create a new baton and helper | Await release before acquisition. |
299
+ | Teardown | `stop()` | Stop application producers. |
300
+ | Intentional navigation | Await `baton.pass()` | Its release callback stops producers and history. |
301
+
302
+ Keep helper loggers in page-lifetime modules. They select the current session on each call.
303
+ Session loggers and sources remain bound to one session. After replacement, old logger writes do
304
+ nothing; old sources, redactors, and controls fail with `stale`. `onChange` refreshes export views
305
+ and producers that need session-bound handles. Stop and lease loss permanently disable the helper.
306
+
307
+ Startup recording uses memory only, with 250,000 JSON characters per area and 1,000,000 total by
308
+ default. `startup: false` disables it. Saved off mode also disables it. Its records transfer once
309
+ into the first owner session. Transfer uses stored redacted records and preserves their identities
310
+ and policy tags. Clear, logout, off, and stop discard the startup buffer.
311
+
312
+ The default database name is `bazario-logging:history:` followed by the application lease key.
313
+ The helper keeps one localStorage metadata record for that database. It contains the application
314
+ key, a random salt and full SHA-256 owner digest, mode, revision, cleanup marker, and level
315
+ overrides. Raw owner IDs stay in memory. A key is saved only for clean persistent history.
316
+ Startup, memory-only, and temporary cleanup-failure keys stay in the page. A memory reload uses
317
+ a new key. Promotion to persistence saves its key only after copying commits.
318
+
319
+ Destructive changes save pending cleanup before deletion. A failed clear keeps that marker and
320
+ uses a fresh, isolated memory session. Successful retry discards that temporary history and rotates
321
+ its key. Metadata failure blocks recording. Managed history rejects unprotected persistence.
322
+ Never attach a raw store beside its managed store. Pass `history.clear` to developer controls.
323
+ Cancel application work that holds snapshots when the account changes; returned data cannot be recalled.
324
+
325
+ ## Behavior
326
+
327
+ `isActive` is an optional admission guard. Returning `false` stops a record before conversion or
328
+ delivery. The hub checks it again after application callbacks. Managed history supplies this guard.
329
+
330
+ - Levels are `debug`, `info`, `warn`, and `error`. Default area level is `info`.
331
+ - Data functions run only after admission. No sinks, stop, levels, and storm limits can prevent admission.
332
+ - The first 1,000 root loggers are cached by area and scope. Further pairs are uncached; one
333
+ `scope` diagnostic reports the limit. Put correlation IDs in child context.
334
+ - Each record is redacted once, then validated, copied, and deeply frozen before sink delivery.
335
+ - Custom redactors must return JSON with dense arrays. Invalid output is dropped and reports `redactor`.
336
+ - Conversion does not call getters or `toJSON`. It bounds strings, structure, and serialized size.
337
+ - Error causes and aggregate errors become structured data. Non-finite numbers become null.
338
+ - Console and default file text include custom Error fields as compact JSON. Their keys and
339
+ values use the same control-character escaping as other Error details.
340
+ - Record validation checks fixed record and Error fields separately from application data. The full
341
+ serialized record must fit `maxRecordSize`. Fixed level values remain intact at small text limits.
342
+ - Default storm allowance is 100 records per area/scope each second. One timer reports suppression.
343
+ - Sink failures do not stop other sinks. Recursive logging from sinks and reporters is rejected.
344
+
345
+ The store defaults and recovery rules below describe the Hopper composition shown above.
346
+ Other providers must state their supported behavior.
347
+
348
+ - Queries use inclusive time bounds and order by time, run ID, then sequence. Empty areas select nothing.
349
+ - Both stores keep the first retained `(runId, seq)` record. Eviction releases its identity.
350
+ - Default budgets are 1,000,000 JSON characters per area, 16,000,000 total, and 24-hour age.
351
+ - IndexedDB also defaults to 20,000 records per area, 250 records per batch, and a one-second flush interval.
352
+ - IndexedDB recovery uses oldest-record eviction for copied and queued fallback records. This replay does not call `onRotate`.
353
+ - Pending plus active database records are limited to 1,000 records and 4,000,000 JSON characters.
354
+ - Expired data is excluded from queries. Disk expiry cleanup runs at open and write, without a timer.
355
+ - `flush()` means local transaction commit. Page exit is best effort; crash survival is not guaranteed.
356
+ - Detach removes sink delivery. The sink's lifecycle owner must stop it separately.
357
+ - A stopped store ignores writes. Its query and clear remain available under their access rules.
358
+
359
+ ## API
360
+
361
+ | Entry | Exports |
362
+ | --- | --- |
363
+ | Root | `createLogging`, `createConsoleSink`, `formatRecord` |
364
+ | `/redaction` | `createRedactor(policy, winnow)` |
365
+ | `/indexeddb` | `createIndexedDbLogStore(options, createStore)` |
366
+ | `/history` | `createLogHistory` |
367
+ | `/export` | `createLogBundle(options, archive)` |
368
+ | `/browser` | `captureGlobalErrors`, `flushOnPageHide`, `collectBrowserEnvironment`, `downloadBlob` |
369
+ | `/sip` | `createSipTools(textRules)`, `describeSipMessage`. The tools expose `redactSipMessage`, `describeSipMessage`, and `SDP_TEXT_RULES`. |
370
+ | `/devtools` | `installConsoleApi` |
371
+
372
+ | Component | Options and methods |
373
+ | --- | --- |
374
+ | Hub | Required `areas`, `redactor`; optional `sinks`, `context`, `levelStorage`, `maxOverrideMs`, `stormLimit`, `scheduler`, clocks, `createRunId`, `onError`, `isActive`. Methods: `getLogger`, `setContext`, `setLevel`, `resetLevel`, `reloadLevels`, `getLevels`, `addSink`, `flush`, `stop`. |
375
+ | Logger | `debug`, `info`, `warn`, `error` accept `(message, data?)`. `child({ scope?, context? })` adds bindings. |
376
+ | Redactor | Policy: `version`, `hash`, `fields`, `unknownFields`, `text`, `limits`. Methods: `redact`, `redactValue`, `redactText`, `mask`, optional `hash`. Resolved `limits` and `version` are public. |
377
+ | Memory store | `maxSizePerArea`, `defaultMaxSizePerArea`, `maxTotalSize`, `maxAgeMs`, `now`, `overflowStrategy`, `onRotate`, `scheduler`, `onError`. Methods: `write`, `query`, `flush`, `clear`, `stop`, `getStatus`. |
378
+ | IndexedDB store | Memory options plus `databaseName`, `indexedDB`, `locks`, `allowUnprotected`, batch/queue/interval/count limits, and `fallback`. |
379
+ | History | Required `lease`, `logging`, `createRedactor`, and `stores`. Optional `createSinks`, `initialMode`, database/storage ports, `memory`, `startup`, `persistent`, rotation callbacks, `onChange`, `onError`. Methods: `getLogger`, `resume`, `setOwner`, `logout`, `setMode`, `clear`, `retryCleanup`, `getSession`, `getStatus`, `stop`. |
380
+ | Bundle | Required `source`, `redactor`, `files`, and the separate archive capability. Optional time bounds, `manifest`, `fileName`, `maxInputSize`, clock, scheduler. Returns `blob`, `fileName`, file counts. |
381
+ | Supplied Hopper copy helper | `copyRecords(from, to, { maxChunkRecords?, maxChunkSize? })`; defaults are 100 records and 1,000,000 JSON characters. It flushes between chunks. |
382
+ | Console sink | Optional minimum `level` and injected `console`. |
383
+ | Custom sink | Implement `LogSink.write(record)`. Optional `flush` and `stop` return promises. |
384
+ | Console API | Inject level controls and optional `clear` and `exportBundle`. Optional `name`, `target`. Returns cleanup. |
385
+
386
+ Redaction defaults: depth 6; strings 16,384 characters; arrays 100 entries; objects 100 keys;
387
+ 1,000 nodes; 65,536 JSON characters per record. Field rules are case-insensitive at every depth:
388
+ `keep`, `drop`, `mask`, `hash`. Unknown fields default to keep. Hash rules require an injected hash.
389
+ An explicit `text: []` disables default text rules. Global non-sticky regular expressions are required.
390
+ Custom patterns and callbacks are trusted application code.
391
+
392
+ SIP redaction preserves protocol structure and replaces identity and credential values. Unknown
393
+ SIP bodies become content-type and byte-count markers. The application owns any selected hasher
394
+ and its dependency. The package does not implement a cryptographic algorithm.
395
+
396
+ Bundle filenames cannot contain paths, reserved device names, or manifest variants. Comparison
397
+ uses NFC and ignores case. Empty files are omitted; the manifest retains their zero counts.
398
+ File names, each file's area list, and the download name are checked before the source query. Its default timestamp is
399
+ the call's start time. The manifest build time is recorded after rendering.
400
+ Custom renderers are synchronous. Built-in formatting yields during large files. Input is limited
401
+ to 32,000,000 UTF-8 bytes by default, including the manifest. Baler checks this limit after
402
+ rendering and before compression. It does not bound renderer allocations or peak heap use.
403
+ The supplied Baler implementation uses raw deflate when available and stored entries otherwise.
404
+ The manifest contains ranges, counts, truncation, warnings, policy versions, store status, and
405
+ redacted application metadata. It contains no history key.
406
+
407
+ Browser listeners are explicit and return cleanup. Environment defaults are language, UTC offset,
408
+ display mode, and online state. Extra fields need explicit selection. Optional reads use a 500 ms
409
+ deadline by default. Unknown field names reject before platform reads. Collection never requests capture or permission and omits device IDs and labels.
410
+
411
+ External services use an application-owned `LogSink.write(record)` implementation. Pass it to
412
+ `sinks` or `addSink`. It receives redacted, frozen records. The application owns service mapping,
413
+ network delivery, and SDK lifecycle. No additional event interface is required.
414
+
415
+ ## Errors
416
+
417
+ Logger writes never throw. Configuration errors and missing capabilities can throw `TypeError`. The hub reports sources
418
+ `data`, `redactor`, `sink`, `storage`, `scope`, and `reentry` through `onError`. Flush and stop attempt
419
+ all sinks and reject with an aggregate error on failure. Hub-generated diagnostics use codes
420
+ `recursive-logging`, `root-logger-cache-limit`, and `stopped`. Callback failures keep their original errors.
421
+
422
+ Store errors have codes `open`, `write`, `read`, `quota`, `overflow`, `ownership`, `clear`, `upgrade`,
423
+ and `rotation`. History errors use `metadata`, `cleanup`, `stale`, `setup`, `migration`, `stopped`,
424
+ and `rotation`. Degraded history cleanup resolves with `cleanupPending: true` and reports the error.
425
+ Failed migration rejects and keeps the previous session. Export and developer actions reject to the caller.
426
+ All acquired stores stay under history cleanup, including setup and temporary cleanup stores.
427
+ A failed store stop remains available for retry and prevents application lease release.
428
+ Managed history reports application sink-stop failures as `setup` errors after its store stops.
429
+ The error cause retains the hub's AggregateError and its original sink errors. Store flush and
430
+ stop reports also retain their original failures as the cause.
431
+ These failures do not prevent account erasure or application lease release. If the store cannot
432
+ stop, history rejects cleanup and retains the lease. Pending source queries drain before retirement ends.
433
+
434
+ ## TypeScript
435
+
436
+ Declarations are provided for ESM and CommonJS. Core declarations need only ES2022 types.
437
+ No entry declaration imports a provider package. Browser, persistence, history, and export entries need DOM types. The logger satisfies the existing
438
+ workspace logger ports without an adapter. Area inference rejects unknown area names.
439
+ Use `createMemoryStore<LogRecord>` for a store that supplies rotation snapshots to Docket export.
440
+ Import `LogRecord` from `@bazariodev/docket`. The export source receives a query argument.
441
+
442
+ ## License
443
+
444
+ [MIT](./LICENSE)
@@ -0,0 +1,14 @@
1
+ export const ENVIRONMENT_FIELDS: Readonly<{
2
+ LANGUAGE: "language";
3
+ UTC_OFFSET_MINUTES: "utcOffsetMinutes";
4
+ DISPLAY_MODE: "displayMode";
5
+ ONLINE: "online";
6
+ USER_AGENT: "userAgent";
7
+ CLIENT_HINTS: "clientHints";
8
+ TIME_ZONE: "timeZone";
9
+ CONNECTION: "connection";
10
+ PERMISSIONS: "permissions";
11
+ DEVICE_COUNTS: "deviceCounts";
12
+ STORAGE: "storage";
13
+ SCREEN: "screen";
14
+ }>;
@@ -0,0 +1,14 @@
1
+ export const ENVIRONMENT_FIELDS: Readonly<{
2
+ LANGUAGE: "language";
3
+ UTC_OFFSET_MINUTES: "utcOffsetMinutes";
4
+ DISPLAY_MODE: "displayMode";
5
+ ONLINE: "online";
6
+ USER_AGENT: "userAgent";
7
+ CLIENT_HINTS: "clientHints";
8
+ TIME_ZONE: "timeZone";
9
+ CONNECTION: "connection";
10
+ PERMISSIONS: "permissions";
11
+ DEVICE_COUNTS: "deviceCounts";
12
+ STORAGE: "storage";
13
+ SCREEN: "screen";
14
+ }>;