pinia-plugin-subscription 0.2.1 → 1.0.0-beta.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.
@@ -0,0 +1,121 @@
1
+ import type { AnyObject } from './index';
2
+ import type Tracer from '../system/Tracer';
3
+ export interface AddTraceListenerOptions {
4
+ filterMode?: TraceListenerFilterMode;
5
+ groupBy?: TraceListenerGroupBy;
6
+ }
7
+ export interface ConsoleTraceEventArgs extends Omit<TraceEvent, 'level' | 'timestamp'> {
8
+ }
9
+ export type ConsoleTraceEvent = Array<string | ConsoleTraceEventArgs>;
10
+ /**
11
+ * A trace listener that supports grouping of trace events.
12
+ */
13
+ export interface ConsoleTraceGroupListener extends Omit<TraceListener, 'filter'> {
14
+ createGroup: (key: string) => void;
15
+ logGroup: (key: string) => void;
16
+ }
17
+ /**
18
+ * Severity of a trace event.
19
+ * `error` events are delivered to matching listeners, or reported with `console.error` when none match.
20
+ */
21
+ export type TraceLevel = 'debug' | 'error';
22
+ /**
23
+ * Cheap descriptor of an event, built before the payload is resolved.
24
+ * Filters receive this object so that discarded events never allocate a payload.
25
+ */
26
+ export interface TraceEventMetadata {
27
+ /** Label describing the event, e.g. `Subscription:Delivery`. */
28
+ label: string;
29
+ /** Severity of the trace event. */
30
+ level: TraceLevel;
31
+ /** Structured, colon separated identifier, e.g. `subscription:delivery`. */
32
+ namespace: string;
33
+ /** Instance the event belongs to, usually a Pinia `store.$id`. */
34
+ scope?: string;
35
+ /** Emitting class name, e.g. `PluginSubscription`. */
36
+ source: string;
37
+ }
38
+ /**
39
+ * A resolved trace event, handed over to listeners.
40
+ */
41
+ export interface TraceEvent extends TraceEventMetadata {
42
+ error?: unknown;
43
+ payload?: AnyObject;
44
+ timestamp: number;
45
+ }
46
+ type TraceFilterCriterionComparator = 'equals' | 'contains' | 'startsWith' | 'endsWith';
47
+ export interface TraceFilterCriterion {
48
+ comparator: TraceFilterCriterionComparator;
49
+ target: keyof TraceEventMetadata;
50
+ value: string;
51
+ }
52
+ export interface TraceFilterFactoryCriterion extends Omit<TraceFilterCriterion, 'comparator'> {
53
+ comparator?: TraceFilterCriterionComparator;
54
+ }
55
+ export type TraceFilterFactoryParam = {
56
+ handler?: TraceHandler;
57
+ filter: TraceFilter | TraceFilterFactoryCriterion[];
58
+ };
59
+ /**
60
+ * Event data. Prefer the function form: it is only invoked when a listener
61
+ * actually consumes the event, keeping unobserved tracing allocation free.
62
+ */
63
+ export type TracePayload = AnyObject | (() => AnyObject);
64
+ /**
65
+ * Determines whether a trace event should be delivered to a listener.
66
+ * @param event The metadata of the trace event.
67
+ * @returns `true` if the event should be delivered to the listener, otherwise `false`.
68
+ */
69
+ export type TraceFilter = (event: TraceEventMetadata) => boolean;
70
+ export interface TraceGroupListener extends ConsoleTraceGroupListener, TraceListener {
71
+ groupBy: TraceListenerGroupBy;
72
+ }
73
+ /**
74
+ * Handles a trace event.
75
+ * @param event The resolved trace event to handle.
76
+ */
77
+ export type TraceHandler = (event: TraceEvent) => void;
78
+ export interface TraceListener {
79
+ /** Evaluated on every event; return `true` to receive it. */
80
+ filter: TraceFilter;
81
+ /** Handles the trace event when it passes the filter. */
82
+ handler: TraceHandler;
83
+ }
84
+ export type TraceListenerFilterMode = 'every' | 'some';
85
+ export type TraceListenerGroupBy = keyof TraceEventMetadata;
86
+ /** Removes a previously registered listener. */
87
+ export type TraceListenerRemover = () => void;
88
+ export type TraceRegistry = {
89
+ /**
90
+ * Registers a trace listener and returns its remover.
91
+ * Listeners are evaluated in registration order.
92
+ * @param listener The trace listener to register.
93
+ * @returns A function that removes the registered listener when called.
94
+ */
95
+ addTraceListener: (listener: TraceListener | TraceFilterFactoryParam, filterMode?: 'every' | 'some') => TraceListenerRemover;
96
+ /**
97
+ * Clears all registered trace listeners.
98
+ */
99
+ clearTraceListeners: () => void;
100
+ /**
101
+ * Creates a new tracer instance.
102
+ * @param source The source of the tracer, usually the class name.
103
+ * @param options Optional tracer options.
104
+ * @returns A new tracer instance.
105
+ */
106
+ createTracer: (source: string, options?: TracerOptions) => Tracer;
107
+ /**
108
+ * Checks if there are any registered trace listeners.
109
+ * @returns True if there are registered trace listeners, otherwise false.
110
+ */
111
+ hasTraceListeners: () => boolean;
112
+ };
113
+ export interface TracerHandler {
114
+ closeGroup: (payload: TraceEvent) => undefined;
115
+ createTracer: (source: string) => Tracer;
116
+ startGroup: (payload: TraceEvent) => undefined;
117
+ }
118
+ export interface TracerOptions {
119
+ scope?: string;
120
+ }
121
+ export {};
@@ -1,6 +1,7 @@
1
1
  import type { DefineStoreOptions, SetupStoreDefinition, StateTree, Store, StoreDefinition, _GettersTree } from "pinia";
2
2
  import type { AnyObject } from "../types";
3
- import type { DefineAStoreDefinition, DefineAStoreSetupContext, EmptyExtensions, PluginStoreOptions, StoreOptions } from "../types/store";
3
+ import type { DefineAStoreDefinition, DefineAStoreSetupContext, EmptyExtensions, StoreOptions } from "../types/store";
4
+ import type { PluginStoreOptions } from "../types/plugin";
4
5
  export declare const itemState: {
5
6
  '@id': undefined;
6
7
  id: undefined;
@@ -27,7 +27,7 @@ Two authoring styles are available:
27
27
  no classes.
28
28
  2. **`PluginSubscriber` abstract class + `Store` subclass** — recommended
29
29
  for richer plugins: the `Store` subclass wraps the pinia store with
30
- helpers (state extension, subscriptions, debug logging).
30
+ helpers (state extension, subscriptions, structured tracing).
31
31
 
32
32
  ## Your first subscriber: a plain object
33
33
 
@@ -37,7 +37,7 @@ Two authoring styles are available:
37
37
  export const myStoreSubscriber = {
38
38
  name: 'my-plugin',
39
39
 
40
- invoke: (context, debug) => {
40
+ invoke: (context) => {
41
41
  // context contains `store`, `options`, `pinia`
42
42
  console.log('store registered', context.store.$id)
43
43
 
@@ -58,7 +58,7 @@ import type { PluginSubscriberInterface } from 'pinia-plugin-subscription'
58
58
  export const myStoreSubscriber: PluginSubscriberInterface = {
59
59
  name: 'my-plugin',
60
60
 
61
- invoke: (context, debug) => {
61
+ invoke: (context) => {
62
62
  console.log('store registered', context.store.$id)
63
63
 
64
64
  return true
@@ -82,17 +82,16 @@ pinia.use(createPlugin([myStoreSubscriber]))
82
82
 
83
83
  | Member | Type | Description |
84
84
  | --- | --- | --- |
85
- | `name` (required) | `string` | Unique plugin name; also used by the debug filter. |
86
- | `invoke` (required) | `(context: PiniaPluginContext, debug: boolean) => boolean` | Called when a store is registered. Return `false` to skip the store. |
85
+ | `name` (required) | `string` | Unique plugin name. |
86
+ | `invoke` (required) | `(context: PiniaPluginContext) => boolean` | Called when a store is registered. Return `false` to skip the store. |
87
87
  | `execution` | `PluginExecutionOptions` | Where/when the subscriber runs — see [Execution policy & SSR](#execution-policy--ssr). |
88
88
  | `hydrationScheduler` | `(run: () => void) => void` | Custom scheduler used when hydration is deferred. |
89
- | `hydrate` | `(context, debug) => void \| Promise<void>` | SSR-safe initialization hook; guaranteed to run before `afterHydration`. |
90
- | `afterHydration` | `(context, debug) => void \| Promise<void>` | Post-hydration lifecycle hook. |
89
+ | `hydrate` | `(context) => void \| Promise<void>` | SSR-safe initialization hook; guaranteed to run before `afterHydration`. |
90
+ | `afterHydration` | `(context) => void \| Promise<void>` | Post-hydration lifecycle hook. |
91
91
  | `resetStoreCallback` | `(store?: Store) => void` | Custom logic executed when a registered store is reset. |
92
92
  | `storeOnActionSubscription` | `{ store, callback }` (getter) | Action subscription forwarded to the returned store's `$onAction`, including after deferred hydration; removed on store disposal. |
93
93
  | `storeMutationSubscription` | `{ store, callback }` (getter) | Mutation subscription forwarded to pinia's `$subscribe`. |
94
94
  | `subscriptions` | `Record<string, PluginSubscription>` | Plugin-specific subscription functions exposed to other plugins. |
95
- | `console` | `Console` | Custom console used for debug logging. |
96
95
 
97
96
  All members except `name` and `invoke` are optional.
98
97
 
@@ -117,7 +116,7 @@ checks when they call your plugin's custom methods.
117
116
  Constructor:
118
117
 
119
118
  ```ts
120
- constructor(pluginName: string, createInstance: CreateInstance, pluginConsole?: Console)
119
+ constructor(pluginName: string, createInstance: CreateInstance)
121
120
  ```
122
121
 
123
122
  | Member | Kind | Description |
@@ -136,20 +135,19 @@ constructor(pluginName: string, createInstance: CreateInstance, pluginConsole?:
136
135
  import { PluginSubscriber, Store } from 'pinia-plugin-subscription'
137
136
 
138
137
  class MyPlugin extends Store {
139
- constructor(store, options, debug = false) {
140
- super(store, options, debug)
138
+ constructor(store, options) {
139
+ super(store, options)
141
140
  this.doSomething()
142
141
  }
143
142
 
144
143
  doSomething() {
145
- this.debugLog('doSomething', this.store.$id)
144
+ this.trace('my-plugin:doSomething', () => ({ storeId: this.store.$id }))
146
145
  }
147
146
  }
148
147
 
149
148
  class MyPluginSubscriber extends PluginSubscriber {
150
149
  constructor() {
151
- super('my-plugin', (store, options, debug, customConsole) =>
152
- new MyPlugin(store, options, debug, customConsole))
150
+ super('my-plugin', (store, options) => new MyPlugin(store, options))
153
151
  }
154
152
  }
155
153
 
@@ -165,13 +163,13 @@ class MyPlugin extends Store {
165
163
  protected override _className: string = 'MyPlugin'
166
164
  protected static override _requiredKeys?: string[] = ['my-plugin-option']
167
165
 
168
- constructor(store, options, debug = false) {
169
- super(store, options, debug)
166
+ constructor(store, options) {
167
+ super(store, options)
170
168
  this.doSomething()
171
169
  }
172
170
 
173
171
  doSomething(): void {
174
- this.debugLog('doSomething', this.store.$id)
172
+ this.trace('my-plugin:doSomething', () => ({ storeId: this.store.$id }))
175
173
  }
176
174
  }
177
175
 
@@ -195,7 +193,7 @@ export const myStoreSubscriber = new MyPluginSubscriber()
195
193
 
196
194
  `Store` wraps a pinia store (`PiniaStore`) and exposes:
197
195
 
198
- **Properties:** `debug`, `options`, `state`, `store`.
196
+ **Properties:** `options`, `state`, `store`.
199
197
 
200
198
  **Methods:**
201
199
 
@@ -208,8 +206,10 @@ export const myStoreSubscriber = new MyPluginSubscriber()
208
206
  | `getStatePropertyValue(propertyName)` | Reads a state property, unwrapping refs. |
209
207
  | `storeSubscribe` (getter/setter) | Factory for pinia's `$subscribe` (`{ store, callback }`). |
210
208
  | `onAction` (getter/setter) | Factory for pinia's `$onAction` (`{ store, callback }`). |
211
- | `static customizeStore(store, options, debug?)` | Recommended factory; guarded by `_requiredKeys` (see above). |
212
- | `debugLog(message, args)` | Conditional logging, active in debug mode. |
209
+ | `static customizeStore(store, options)` | Recommended factory; guarded by `_requiredKeys` (see above). |
210
+ | `trace(label, payload?, scope?, namespace?)` | Emits a structured trace event. The default scope is the wrapped store ID. |
211
+ | `traceError(label, error, payload?, scope?, namespace?)` | Emits an error event; if no listener matches, reports it with `console.error` without rethrowing it. |
212
+ | `classNameAddedToTraceNamespace` (getter/setter) | Controls whether the store class name is prefixed to trace labels; enabled by default. |
213
213
  | `stateHas(property)` / `storeHas(property)` | Safe `hasOwnProperty` checks on state / store. |
214
214
  | `getValue(value)` | Unwraps a ref or returns the value as-is. |
215
215
  | `isOptionApi()` | Whether the store uses the options API. |
@@ -234,7 +234,7 @@ export const persistedStateSubscriber = {
234
234
  hydrationScheduler: (run) => {
235
235
  nextTick(() => run())
236
236
  },
237
- invoke: (context, debug) => {
237
+ invoke: (context) => {
238
238
  // ...
239
239
  return true
240
240
  }
@@ -256,7 +256,7 @@ export const persistedStateSubscriber: PluginSubscriberInterface = {
256
256
  hydrationScheduler: (run) => {
257
257
  nextTick(() => run())
258
258
  },
259
- invoke: (context, debug) => {
259
+ invoke: (context) => {
260
260
  // ...
261
261
  return true
262
262
  }
@@ -360,6 +360,35 @@ const mySubscriber = {
360
360
  This keeps the server render deterministic and prevents browser-only
361
361
  initialization from crashing Nuxt or SSR builds.
362
362
 
363
+ ### Emitting trace events
364
+
365
+ Use `trace()` and `traceError()` from a `Store` subclass to emit structured
366
+ events. The first argument is a human-readable label; by default the store
367
+ class name is prefixed to it and the normalized label becomes the namespace.
368
+ Pass an explicit fourth `namespace` argument to control the namespace, or set
369
+ `classNameAddedToTraceNamespace = false` to omit the class prefix. Prefer a
370
+ payload factory so data is not built unless a listener consumes the event:
371
+
372
+ ```ts
373
+ class CartStore extends Store {
374
+ reportItems(): void {
375
+ this.trace(
376
+ 'cart:items',
377
+ () => ({ items: this.state.items }),
378
+ this.store.$id,
379
+ 'cart:items'
380
+ )
381
+ }
382
+ }
383
+ ```
384
+
385
+ `createTracerRegistry()` and `createConsoleTraceListener()` are available
386
+ from `pinia-plugin-subscription/helpers`. Register listeners on a registry
387
+ and pass its `createTracer` factory through `createPlugin()` options to
388
+ connect them to plugin and store events. Listener filters can be callbacks
389
+ over event metadata or criteria using `equals`, `contains`, `startsWith` and
390
+ `endsWith` comparisons.
391
+
363
392
  ## Typing per-store options: `StoreOptionsExtensions` (TypeScript)
364
393
 
365
394
  This package is the only one allowed to augment pinia's
@@ -395,7 +424,7 @@ Guidelines:
395
424
  - Always declare **optional** keys with names specific enough to avoid
396
425
  collisions (prefer `myPluginOption` over `options`).
397
426
  - At runtime, merged options are delivered to your subscriber's
398
- `invoke(context, debug)` through `context.options.storeOptions` — no
427
+ `invoke(context)` through `context.options.storeOptions` — no
399
428
  extra wiring needed.
400
429
  - Never redeclare `DefineStoreOptionsBase` or `PiniaCustomProperties`; only
401
430
  this package owns those augmentations.
@@ -403,6 +432,6 @@ Guidelines:
403
432
  ## Reference implementation
404
433
 
405
434
  The demo app ships a complete plugin implementation in
406
- [`src/demo/extending-pinia-store/`](../src/demo/extending-pinia-store/): a
435
+ [`demo/`](../demo/): a
407
436
  `PluginSubscriber` subclass, a `Store` subclass, option typing and SSR
408
437
  guards. Run `npm run dev` to explore it live.
@@ -22,8 +22,8 @@ Plugins built on `pinia-plugin-subscription` share a common contract:
22
22
  - **`$reset` on registered stores** — every store registered by the plugin
23
23
  gains a `$reset()` method that restores its initial state, including
24
24
  setup-style stores where pinia does not provide one natively.
25
- - **Per-plugin debug mode** — pass plugin names to `createPlugin()` to
26
- enable detailed logging for those plugins only.
25
+ - **Runtime-filtered tracing** — register trace listeners and filter events by
26
+ source, namespace, scope, or level.
27
27
  - **SSR-safe execution** — each plugin declares where (`client`, `server`
28
28
  or `both`) and when (`immediate` or deferred after hydration) it runs.
29
29
  - **State rollback on action failure** — actions listed in the
@@ -46,15 +46,14 @@ Compatible plugins expose **subscribers** that are passed to
46
46
  ```js
47
47
  import { createApp } from 'vue'
48
48
  import { createPinia } from 'pinia'
49
- import { createPlugin, PLUGIN_NAME } from 'pinia-plugin-subscription'
49
+ import { createPlugin } from 'pinia-plugin-subscription'
50
50
  import { myStoreSubscriber } from './plugins/my-store'
51
51
  import App from './App.vue'
52
52
 
53
53
  const app = createApp(App)
54
54
  const pinia = createPinia()
55
55
 
56
- // Second argument (optional): names of the plugins to debug.
57
- pinia.use(createPlugin([myStoreSubscriber], [PLUGIN_NAME]))
56
+ pinia.use(createPlugin([myStoreSubscriber]))
58
57
 
59
58
  app.use(pinia)
60
59
  app.mount('#app')
@@ -66,12 +65,12 @@ The TypeScript version is identical — `createPlugin()` is fully typed and
66
65
  requires no generics:
67
66
 
68
67
  ```ts
69
- import { createPlugin, PLUGIN_NAME } from 'pinia-plugin-subscription'
68
+ import { createPlugin } from 'pinia-plugin-subscription'
70
69
  import type { PluginSubscriberInterface } from 'pinia-plugin-subscription'
71
70
 
72
71
  const subscribers: PluginSubscriberInterface[] = [myStoreSubscriber]
73
72
 
74
- pinia.use(createPlugin(subscribers, [PLUGIN_NAME]))
73
+ pinia.use(createPlugin(subscribers))
75
74
  ```
76
75
 
77
76
  ## Defining stores: `defineAStore`
@@ -232,9 +231,7 @@ export const useMyStore = defineAStoreCtx('myStore', (ctx) => {
232
231
 
233
232
  `defineAStoreCtx()` enables the `enhancedStore` store option automatically.
234
233
  The internal setup-context lookup is cleaned up as soon as the store is
235
- associated (and on store dispose), which limits map growth over time. When
236
- `storeOptions.debug` is `true`, setup-context map size transitions are
237
- logged for observability.
234
+ associated (and on store dispose), which limits map growth over time.
238
235
 
239
236
  ## `$reset` on registered stores
240
237
 
@@ -316,17 +313,46 @@ try {
316
313
  (forwarded to the action via `apply()`).
317
314
  - The original error is rethrown after the state is restored.
318
315
 
319
- ## Debug mode
316
+ ## Tracing
320
317
 
321
- Pass the names of the plugins you want to inspect as the second argument of
322
- `createPlugin()`:
318
+ Register listeners independently from plugin construction:
323
319
 
324
320
  ```js
325
- pinia.use(createPlugin([myStoreSubscriber], [PLUGIN_NAME, 'my-plugin']))
321
+ import {
322
+ createConsoleTraceListener,
323
+ createTracerRegistry
324
+ } from 'pinia-plugin-subscription/helpers'
325
+ import {
326
+ createPlugin
327
+ } from 'pinia-plugin-subscription'
328
+
329
+ const traceRegistry = createTracerRegistry()
330
+
331
+ traceRegistry.addTraceListener({
332
+ ...createConsoleTraceListener(),
333
+ filter: event => event.scope === 'cart'
334
+ })
335
+
336
+ pinia.use(createPlugin([myStoreSubscriber], {
337
+ createTracer: traceRegistry.createTracer
338
+ }))
326
339
  ```
327
340
 
328
- Matching plugins then emit detailed logs (registration, execution policy,
329
- hydration lifecycle).
341
+ Filters run in real time and can target a source, namespace, store scope, or
342
+ trace level without passing diagnostic options through subscribers.
343
+ You can also use comparison criteria (`equals`, `contains`, `startsWith` or
344
+ `endsWith`) instead of a filter callback; criteria are combined with `every`
345
+ by default or `some` when requested. If criteria are provided without a
346
+ handler, the registry uses its styled console listener.
347
+
348
+ ```js
349
+ traceRegistry.addTraceListener({
350
+ filter: [
351
+ { target: 'scope', value: 'cart' },
352
+ { target: 'namespace', comparator: 'startsWith', value: 'pluginsubscription:subscription:' }
353
+ ]
354
+ }, 'every')
355
+ ```
330
356
 
331
357
  ## SSR / Nuxt
332
358
 
@@ -334,16 +360,48 @@ The package is SSR-safe by design. Each compatible plugin declares its own
334
360
  execution policy, so nothing browser-only runs on the server unless the
335
361
  plugin explicitly allows it.
336
362
 
363
+ For request isolation, create the app, Pinia instance, trace registry, and
364
+ plugin inside the per-request app factory. Do not store the registry or the
365
+ `createPlugin()` result at module scope: both retain request-associated
366
+ listeners or plugin state. A new registry per request also avoids races
367
+ between concurrent requests; do not clear a shared registry when one request
368
+ finishes.
369
+
370
+ ```js
371
+ import { createSSRApp } from 'vue'
372
+ import { createPinia } from 'pinia'
373
+ import { createPlugin } from 'pinia-plugin-subscription'
374
+ import { createTracerRegistry } from 'pinia-plugin-subscription/helpers'
375
+ import App from './App.vue'
376
+ import { subscribers } from './subscribers'
377
+
378
+ export function createRequestApp(traceListener) {
379
+ const app = createSSRApp(App)
380
+ const pinia = createPinia()
381
+ const traceRegistry = createTracerRegistry()
382
+
383
+ traceRegistry.addTraceListener(traceListener)
384
+ pinia.use(createPlugin(subscribers, {
385
+ createTracer: traceRegistry.createTracer
386
+ }))
387
+ app.use(pinia)
388
+
389
+ return { app, pinia }
390
+ }
391
+ ```
392
+
393
+ Call `createRequestApp()` once for each incoming SSR request. The request's
394
+ listener is then reachable only through that request's registry and plugin.
395
+
337
396
  When a plugin needs a runtime-specific scheduler or environment override —
338
397
  without forcing every app to re-declare the plugin's execution policy —
339
398
  register it through the dedicated hydration helper:
340
399
 
341
400
  ```js
342
401
  import { nextTick } from 'vue'
343
- import { createHydrationPlugin, PLUGIN_NAME } from 'pinia-plugin-subscription'
402
+ import { createHydrationPlugin } from 'pinia-plugin-subscription'
344
403
 
345
404
  pinia.use(createHydrationPlugin([persistedStateSubscriber], {
346
- debug: [PLUGIN_NAME],
347
405
  hydrationScheduler: (run) => {
348
406
  nextTick(() => run())
349
407
  }
@@ -355,7 +413,6 @@ pinia.use(createHydrationPlugin([persistedStateSubscriber], {
355
413
 
356
414
  | Option | Type | Description |
357
415
  | --- | --- | --- |
358
- | `debug` | `string[]` | Plugin names to debug. |
359
416
  | `execution` | `PluginExecutionOptions` | Global execution policy override. |
360
417
  | `hydrationScheduler` | `(run: () => void) => void` | Scheduler used for deferred hydration. |
361
418
  | `runtimeEnvironment` | `'client' \| 'server'` | Forces the runtime environment. |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pinia-plugin-subscription",
3
- "version": "0.2.1",
3
+ "version": "1.0.0-beta.1",
4
4
  "description": "Pinia plugin for Vue.js that helps building Pinia plugins by centralizing subscriber registration and providing a Store base class for store helpers.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -69,7 +69,8 @@
69
69
  "dev:library": "vite",
70
70
  "test": "vitest",
71
71
  "coverage": "vitest run --coverage",
72
- "build": "vitest && vite build && tsc --emitDeclarationOnly --outDir dist && node scripts/measure-bundle.mjs",
72
+ "build": "vitest && vite build && tsc --emitDeclarationOnly --outDir dist && npm run check:leaks && node scripts/measure-bundle.mjs",
73
+ "check:leaks": "node scripts/check-dist-leaks.mjs",
73
74
  "bundle:check": "vite build --config vite.config.ts && node scripts/measure-bundle.mjs",
74
75
  "build:demo": "vite --config vite.demo.config.ts build",
75
76
  "preview": "vite preview --config vite.demo.config.ts"