@phin-tech/dottie 0.10.0 → 0.11.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.
@@ -1,26 +1,34 @@
1
- import { type DottieFile, type Element, type Marker, type Page } from "./model.ts";
1
+ import { type ClickValue, type DottieFile, type Element, type Marker, type Page } from "./model.ts";
2
2
  /** Jump to scene `scene` (an index into the page's markers) and play on to the next; open `url` in a new tab; or,
3
3
  * in <dottie-player> only, show page `page` (an index into the file's pages), where the element's link points. Any
4
- * of them may first `set` and `toggle` page variables (ClickAction.set, .toggle), and a click may do only that. */
4
+ * of them may first `set` and `toggle` page variables (ClickAction.set, .toggle), and a click may do only that. Or go to
5
+ * `href`, the exported web page that holds the page a `link` points at, when a drawing is exported one page per file. */
5
6
  export type Click = ({
6
7
  scene: number;
7
8
  } | {
8
9
  url: string;
9
10
  } | {
10
11
  page: number;
12
+ } | {
13
+ href: string;
11
14
  } | {
12
15
  state: true;
13
16
  }) & {
14
- set?: Record<string, boolean>;
17
+ set?: Record<string, ClickValue>;
15
18
  toggle?: string[];
16
19
  reset?: true;
20
+ run?: string;
17
21
  };
18
22
  /** The marker called `name`: an exact match, else one that differs only in case. */
19
23
  export declare function sceneNamed(name: string, markers: Marker[]): number;
20
24
  /** The page's clickable elements and what each click does, by element id. A scene named by no marker, or a link
21
25
  * that isn't allowed, makes nothing clickable; an element with no `click` opens its `url` when that is a web link. Given the whole file, an element whose `link` points at an element on
22
- * another page shows that page (the HTML export holds one page, so it can't). */
23
- export declare function clickTargets(page: Page, file?: DottieFile): Map<string, Click>;
26
+ * another page shows that page. `hrefs` names, by the id of an element a link points at, the web page that shows it
27
+ * (`<dottie-player links>`): a page exported on its own reaches the others through them. */
28
+ export declare function clickTargets(page: Page, file?: DottieFile, hrefs?: Record<string, string>): Map<string, Click>;
29
+ /** `href` if it is a page beside this one (a relative `.html` path, maybe with a `#fragment`), else null: no scheme,
30
+ * so never `javascript:` or another site. */
31
+ export declare function pageHref(href: unknown): string | null;
24
32
  /** `e` as it looks hovered (HoverLook.apply). */
25
33
  export declare function hoveredElement(e: Element): Element;
26
34
  /** `page` with `ids` drawn as they look hovered. */
@@ -2,8 +2,12 @@ import type { Face } from "./metrics.ts";
2
2
  /** What follows the runtime; DOTTIE_DATA, DOTTIE_OPTIONS and DOTTIE_FONTS are replaced with JSON. Plain ES5, unminified. */
3
3
  export declare const LOADER = ";(function () {\n var data = DOTTIE_DATA, options = DOTTIE_OPTIONS, fonts = DOTTIE_FONTS;\n var script = document.currentScript;\n if (window.Dottie.addFonts) window.Dottie.addFonts(Object.keys(fonts), fonts);\n function attr(el, name, value) { el.setAttribute(name.replace(/[A-Z]/g, function (c) { return \"-\" + c.toLowerCase(); }), value); }\n function mount() {\n var player = document.createElement(\"dottie-player\"), k;\n for (k in options) attr(player, k, options[k]);\n if (script) for (k in script.dataset) if (k !== \"target\") attr(player, k, script.dataset[k]);\n var target = script && script.dataset.target ? document.querySelector(script.dataset.target) : null;\n if (target) target.appendChild(player);\n else if (script && script.parentNode && script.parentNode !== document.head) script.parentNode.insertBefore(player, script);\n else document.body.appendChild(player);\n player.setFile(window.Dottie.parseDottie(data));\n }\n if (document.readyState === \"loading\" && (!script || script.parentNode === document.head || script.dataset.target)) {\n document.addEventListener(\"DOMContentLoaded\", mount);\n } else {\n mount();\n }\n})();\n";
4
4
  export interface CompileOptions {
5
- /** Page name or 1-based number; only that page goes in. The first page by default. */
5
+ /** Page name or 1-based number; only that page goes in. The first page by default. With `allPages`, the page to
6
+ * start on. */
6
7
  page?: string | number;
8
+ /** Every page goes in, and links between them work: each page shown gets its own address (`#` and its name), so
9
+ * the browser's Back and Forward go between them (the player's `history`). */
10
+ allPages?: boolean;
7
11
  /** Show this moment (seconds) as a still. */
8
12
  at?: number;
9
13
  clean?: boolean;
@@ -11,6 +15,9 @@ export interface CompileOptions {
11
15
  noAutoplay?: boolean;
12
16
  noLoop?: boolean;
13
17
  label?: string;
18
+ /** The click-logic engine (dist/dottie-logic.min.js): with it the page's logic may run (ADR 0007), and the player
19
+ * gets allow-logic; without it a click does its own set and toggle. */
20
+ logicEngine?: string;
14
21
  /** A bundled font's file (`fontFile`) as base64, so the drawing carries the fonts it uses; without it the
15
22
  * player fetches them from beside itself. dottie-compile reads them from dist/fonts. */
16
23
  fontData?: (family: string, face: Face) => string | undefined;
@@ -0,0 +1,51 @@
1
+ /** A page variable's value: true or false, or a number. */
2
+ export type Var = boolean | number;
3
+ export type Expr = {
4
+ k: "lit";
5
+ v: Var;
6
+ } | {
7
+ k: "name";
8
+ name: string;
9
+ } | {
10
+ k: "metric";
11
+ metric: string;
12
+ target: string;
13
+ } | {
14
+ k: "not";
15
+ e: Expr;
16
+ } | {
17
+ k: "neg";
18
+ e: Expr;
19
+ } | {
20
+ k: "bin";
21
+ op: string;
22
+ l: Expr;
23
+ r: Expr;
24
+ } | {
25
+ k: "call";
26
+ fn: string;
27
+ args: Expr[];
28
+ };
29
+ /** The metrics an expression can read (PageState.metricNames). */
30
+ export declare const METRIC_NAMES: string[];
31
+ /** As a condition reads it: a number holds unless it is 0 (or not a number). */
32
+ export declare const truthy: (v: Var) => boolean;
33
+ /** As arithmetic reads it: true is 1, false 0. */
34
+ export declare const num: (v: Var) => number;
35
+ /** `text` parsed, or null when it isn't an expression. Remembered, since a frame reads the same conditions often. */
36
+ export declare function parseExpr(text: string): Expr | null;
37
+ /** What a metric reads; null when it can't be had (every comparison with it then fails). */
38
+ export type MetricRead = (metric: string, target: string) => number | null;
39
+ /** The value with these variables (a missing one is false). */
40
+ export declare function evaluate(e: Expr, state: Record<string, Var>, metric?: MetricRead | null): Var;
41
+ /** The variable names it reads, in order, each once. */
42
+ export declare function exprNames(e: Expr): string[];
43
+ /** The metrics it reads. */
44
+ export declare function exprMetrics(e: Expr): {
45
+ metric: string;
46
+ target: string;
47
+ }[];
48
+ /** A variable's value as text shows it (Var.description): true or false, a number to two places. */
49
+ export declare function describeVar(v: Var): string;
50
+ /** `text` with each `{expression}` that reads only the page's variables replaced by its value (PageState.filling). */
51
+ export declare function filling(text: string, values: Record<string, Var>): string;
@@ -20,4 +20,4 @@ export interface RenderOptions {
20
20
  /** One page of a drawing as an SVG document, as Dottie's own SVG export draws it. */
21
21
  export declare function render(source: string | DottieFile | unknown, options?: RenderOptions): string;
22
22
  /** The library's version. */
23
- export declare const VERSION = "0.10.0";
23
+ export declare const VERSION = "0.11.1";
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,9 @@
1
+ import type { ClickEvent, LogicResult } from "./logic.ts";
2
+ import type { Var } from "./expression.ts";
3
+ interface Engine {
4
+ loadLogic(): Promise<void>;
5
+ runLogic(source: string, handler: string, state: Record<string, Var>, event: ClickEvent): LogicResult;
6
+ }
7
+ /** The engine, loaded; rejects where it can't be had. */
8
+ export declare function logicEngine(): Promise<Engine>;
9
+ export {};
@@ -0,0 +1,2 @@
1
+ /** Adds what's missing; safe to call more than once. */
2
+ export declare function polyfill(): void;
@@ -0,0 +1,32 @@
1
+ import type { Var } from "./expression.ts";
2
+ /** What a handler is told about the click. */
3
+ export interface ClickEvent {
4
+ /** The clicked element's id, anchor and first line of text. */
5
+ id: string;
6
+ anchor: string | null;
7
+ text: string | null;
8
+ /** Seconds into the page's timeline, and the scene playing. */
9
+ time: number;
10
+ scene: string | null;
11
+ }
12
+ /** New variable values, or why there are none (the state then stays as it was). */
13
+ export type LogicResult = {
14
+ ok: true;
15
+ set: Record<string, Var>;
16
+ } | {
17
+ ok: false;
18
+ error: string;
19
+ };
20
+ /** How much a handler may do: QuickJS checks in every 10,000 or so operations, and this many check-ins is the
21
+ * limit (a few million operations, well under a second). */
22
+ export declare const OPERATION_BUDGET = 400;
23
+ /** How much memory a handler may use. */
24
+ export declare const MEMORY_LIMIT: number;
25
+ /** Longest logic a page may have, in characters. */
26
+ export declare const MAX_SOURCE: number;
27
+ /** Loads the engine (once). */
28
+ export declare function loadLogic(): Promise<void>;
29
+ /** Whether the engine is loaded, so `runLogic` can be called. */
30
+ export declare function logicReady(): boolean;
31
+ /** Runs `handler` from `source` for a click; the engine must be loaded. */
32
+ export declare function runLogic(source: string, handler: string, state: Record<string, Var>, event: ClickEvent): LogicResult;
@@ -1,5 +1,6 @@
1
1
  import type { Element, Flow, Gauge, Page, RGBA } from "./model.ts";
2
2
  import { type MetricTerm } from "./state.ts";
3
+ import type { Var } from "./expression.ts";
3
4
  /** A connector's or shape's metrics in one frame (ElementMetrics). */
4
5
  export interface ElementMetrics {
5
6
  throughput: number;
@@ -44,7 +45,7 @@ export declare const queueKey: (shape: string, flow: string) => string;
44
45
  * and the frame's metrics. `since` is the seconds since the page's state last changed
45
46
  * (null: `t`), and `carried` how much each flow's queue held at that moment: since then a queue grows by its
46
47
  * backlog, or drains at the room its way on has left. */
47
- export declare function flowFrame(flows: Flow[] | null, frame: Page, t: number, values: Record<string, boolean> | null, offline?: Set<string>, sinceChange?: number | null, carried?: Record<string, number>): {
48
+ export declare function flowFrame(flows: Flow[] | null, frame: Page, t: number, values: Record<string, Var> | null, offline?: Set<string>, sinceChange?: number | null, carried?: Record<string, number>): {
48
49
  packets: Element[];
49
50
  metrics: FrameMetrics;
50
51
  routes: Map<string, FlowRoute[]>;
@@ -62,13 +63,13 @@ export interface Finish {
62
63
  /** For each of `elements`, the overrides that compare metrics that hold and the reading a gauge shows (easing, over
63
64
  * `GAUGE_EASE` seconds since the state changed, from its reading then, `carried`); with no values ("all", the
64
65
  * editor), nothing. The readings come back to carry over. */
65
- export declare function finishPlan(elements: Element[], metrics: FrameMetrics, values: Record<string, boolean> | null, frame: Page, since?: number, carried?: Record<string, number>): {
66
+ export declare function finishPlan(elements: Element[], metrics: FrameMetrics, values: Record<string, Var> | null, frame: Page, since?: number, carried?: Record<string, number>): {
66
67
  plan: Finish[];
67
68
  readings: Record<string, number>;
68
69
  };
69
70
  /** `elements` with the looks of overrides that compare metrics applied, and gauges drawn as plain elements; with
70
71
  * no values ("all", the editor), both are left as they are (`finishPlan`). */
71
- export declare function finishing(elements: Element[], metrics: FrameMetrics, values: Record<string, boolean> | null, frame: Page, flows: Flow[], since?: number, carried?: Record<string, number>): {
72
+ export declare function finishing(elements: Element[], metrics: FrameMetrics, values: Record<string, Var> | null, frame: Page, flows: Flow[], since?: number, carried?: Record<string, number>): {
72
73
  elements: Element[];
73
74
  readings: Record<string, number>;
74
75
  };
@@ -1,3 +1,4 @@
1
+ import type { Var } from "./expression.ts";
1
2
  export interface RGBA {
2
3
  r: number;
3
4
  g: number;
@@ -32,6 +33,8 @@ export interface Style {
32
33
  /** Which colors are palette colors, by name (ADR 0005); set from the page's palette when it is read. */
33
34
  named?: ColorNames | null;
34
35
  }
36
+ /** What a click gives a variable (ClickValue): a value, or an expression over the state before the click. */
37
+ export type ClickValue = Var | string;
35
38
  /** Palette names for an element's colors (Theme.swift's ColorNames). */
36
39
  export interface ColorNames {
37
40
  fill: string | null;
@@ -98,10 +101,12 @@ export interface EmbeddedImage {
98
101
  export interface ClickAction {
99
102
  scene: string | null;
100
103
  url: string | null;
101
- set: Record<string, boolean> | null;
104
+ set: Record<string, ClickValue> | null;
102
105
  toggle: string[] | null;
103
106
  /** Puts every page variable back to its starting value first. */
104
107
  reset?: boolean | null;
108
+ /** A handler in the page's `logic` to run (ADR 0007); where logic may not run, `set` and `toggle` stand in. */
109
+ run?: string | null;
105
110
  }
106
111
  /** How a clickable element looks under the pointer or with keyboard focus; null keeps its own. */
107
112
  /** A change to an element while `when` holds (StateOverride): a field left null keeps the element's own. */
@@ -280,12 +285,14 @@ export interface Page {
280
285
  layers: Layer[];
281
286
  elements: Element[];
282
287
  theme: string | null;
288
+ /** JavaScript defining the handlers clicks `run` (ADR 0007), run only on clicks, sealed, where allowed. */
289
+ logic?: string | null;
283
290
  /** A theme of the page's own (ADR 0005), in place of `theme`. */
284
291
  customTheme?: CustomTheme | null;
285
292
  timeline: Timeline | null;
286
293
  symbols: Symbol[] | null;
287
294
  /** The page's variables and their starting values (DottieDocument.variables). */
288
- variables: Record<string, boolean> | null;
295
+ variables: Record<string, Var> | null;
289
296
  /** Traffic routed over the connectors drawn in each frame (DottieDocument.flows; see flows.ts). */
290
297
  flows: Flow[] | null;
291
298
  }
@@ -330,7 +337,7 @@ export interface DottieFile {
330
337
  warnings: string[];
331
338
  }
332
339
  /** The newest file version this library was written for. Newer files still load, with a warning. */
333
- export declare const SUPPORTED_VERSION = 2;
340
+ export declare const SUPPORTED_VERSION = 3;
334
341
  export declare const KINDS: readonly string[];
335
342
  /** The fields dottie.js reads, per object; the schema test checks they're exactly the schema's. Anything else
336
343
  * in a file is newer than this version and is reported in `warnings`. */
@@ -1,6 +1,7 @@
1
1
  import { type Click } from "./clicks.ts";
2
2
  import { type DottieFile, type Page, type RGBA } from "./model.ts";
3
3
  import { type Plan } from "./svg.ts";
4
+ import type { Var } from "./expression.ts";
4
5
  /** Finds a page by name (any case) or 1-based number; the first page when `spec` is empty or matches nothing. */
5
6
  export declare function pageIndex(file: DottieFile, spec: string | null | undefined): number;
6
7
  export declare function withRoughness(page: Page, roughness: number): Page;
@@ -13,7 +14,7 @@ export declare class Playback {
13
14
  /** Whether each scene loops (Marker.loop). */
14
15
  readonly sceneLoops: boolean[];
15
16
  /** The page's variables now: their starting values until clicks change them. */
16
- state: Record<string, boolean>;
17
+ state: Record<string, Var>;
17
18
  /** When the variables last changed (performance.now() milliseconds): backlog piles up from then (ADR 0003). */
18
19
  stateSince: number;
19
20
  /** A still (the `at` attribute): backlog is drawn for the page time, not the wall clock. */
@@ -48,10 +49,12 @@ export declare class Playback {
48
49
  private order;
49
50
  frame: number;
50
51
  onFrame: ((frame: number) => void) | null;
51
- /** `file` is the drawing `page` belongs to, so links to its other pages are clickable. */
52
+ /** `file` is the drawing `page` belongs to, so links to its other pages are clickable; `hrefs`, the web pages that
53
+ * show the elements links point at, for pages that aren't in it (clickTargets). */
52
54
  constructor(page: Page, into: Element, options?: {
53
55
  clean?: boolean;
54
56
  file?: DottieFile;
57
+ hrefs?: Record<string, string>;
55
58
  });
56
59
  get frameCount(): number;
57
60
  get posterFrame(): number;
@@ -107,6 +110,9 @@ export declare class DottiePlayer extends ElementBase {
107
110
  private stillTime;
108
111
  private visible;
109
112
  private observer;
113
+ /** The page last shown, so `page` fires only when it changes. */
114
+ private shownPage;
115
+ private readonly onPopState;
110
116
  constructor();
111
117
  connectedCallback(): void;
112
118
  disconnectedCallback(): void;
@@ -119,6 +125,10 @@ export declare class DottiePlayer extends ElementBase {
119
125
  /** Shows the file's page; then `mounted`. A big animation is planned in a worker first (planner.ts), and what was
120
126
  * showing stays until it is ready; a newer mount, file or failed load in the meantime wins. */
121
127
  private mount;
128
+ /** With `history`, the page the address names after its `#` (by name, any case), if it names one. */
129
+ private addressedPage;
130
+ /** The `links` attribute read: element id to web page, the pages that can't be named left out. */
131
+ private hrefs;
122
132
  private mountPage;
123
133
  get paused(): boolean;
124
134
  /** The moment shown: a still's own time (its `at`, or the poster), else the current frame's. */
@@ -134,10 +144,14 @@ export declare class DottiePlayer extends ElementBase {
134
144
  /** Does what clicking element `id` does: jumps to its scene and plays on to the next (with reduced motion, or in a
135
145
  * still, straight to where that stops), opens its link in a new tab, or shows its page. */
136
146
  activate(id: string): void;
147
+ /** Runs handler `handler` from the page's logic for a click on `id` (DottieDocument.clicked). Clicks take turns, so
148
+ * each handler sees the state the one before left. */
149
+ private logicQueue;
150
+ private runLogic;
137
151
  /** The page's variables now. */
138
- get variables(): Record<string, boolean>;
152
+ get variables(): Record<string, Var>;
139
153
  /** Gives the page's variables these values (the others keep theirs), redraws, and fires `state`. */
140
- setState(values: Record<string, boolean>): void;
154
+ setState(values: Record<string, Var>): void;
141
155
  /** Redraws, while the page itself stands still, as long as a clicked copy is playing through. */
142
156
  private scheduleReplay;
143
157
  private setPlaying;
@@ -1,8 +1,8 @@
1
- import { type ClickAction, type Element, type Page, type StateOverride } from "./model.ts";
1
+ import { type Var } from "./expression.ts";
2
+ import { type ClickAction, type ClickValue, type Element, type Page, type StateOverride } from "./model.ts";
2
3
  /** Whether `name` can name a variable: a letter or underscore, then letters, digits and underscores. */
3
4
  export declare function isName(name: string): boolean;
4
- /** The metrics a condition can compare (ADR 0003), as `load(workers) > 0.9`. */
5
- export declare const METRIC_NAMES: string[];
5
+ export { METRIC_NAMES } from "./expression.ts";
6
6
  /** A comparison of a metric in a condition (PageState.MetricTerm). */
7
7
  export interface MetricTerm {
8
8
  metric: string;
@@ -10,25 +10,24 @@ export interface MetricTerm {
10
10
  op: string;
11
11
  value: number;
12
12
  }
13
- /** `term` (trimmed) read as a metric comparison, or null. */
13
+ /** `term` read as one metric comparison, `metric(target) op number`, or null. */
14
14
  export declare function metricTerm(term: string): MetricTerm | null;
15
- /** Whether `condition` compares a metric anywhere. */
15
+ /** Whether `condition` reads a metric anywhere. */
16
16
  export declare function mentionsMetric(condition: string): boolean;
17
- /** The variable names `condition` reads, or null when it isn't one: names, each optionally after `!`, or metric
18
- * comparisons, joined by `&&` and `||` (`&&` binds tighter). */
17
+ /** The variable names `condition` reads, or null when it isn't one (ADR 0007's expressions). */
19
18
  export declare function conditionNames(condition: string): string[] | null;
20
19
  /** Whether `condition` holds with these values, and `metric` giving the frame's metrics. A missing variable is
21
- * false, and so is a comparison whose metric can't be had; a condition that can't be read never holds. */
22
- export declare function holds(condition: string, state: Record<string, boolean>, metric?: ((m: MetricTerm) => number | null) | null): boolean;
20
+ * false, and a comparison with a metric that can't be had fails; a condition that can't be read never holds. */
21
+ export declare function holds(condition: string, state: Record<string, Var>, metric?: ((m: MetricTerm) => number | null) | null): boolean;
23
22
  /** Which elements a page shows as far as its variables go: their starting values ("initial"), these values (a
24
23
  * missing one takes its starting value), or every element ("all"). */
25
- export type StateChoice = "initial" | "all" | Record<string, boolean>;
24
+ export type StateChoice = "initial" | "all" | Record<string, Var>;
26
25
  /** The values `choice` gives each variable; null for "all". */
27
- export declare function stateValues(page: Page, choice: StateChoice): Record<string, boolean> | null;
26
+ export declare function stateValues(page: Page, choice: StateChoice): Record<string, Var> | null;
28
27
  /** The ids of the elements whose `when` doesn't hold under `choice`. */
29
28
  export declare function stateHidden(page: Page, choice: StateChoice): Set<string>;
30
29
  /** The overrides of `e` that hold under `values`, in order; none for null ("all", the editor). */
31
- export declare function activeOverrides(e: Element, values: Record<string, boolean> | null): StateOverride[];
30
+ export declare function activeOverrides(e: Element, values: Record<string, Var> | null): StateOverride[];
32
31
  /** Only what an override comparing metrics may change: its looks (ADR 0003). */
33
32
  export declare function looksOnly(o: StateOverride): StateOverride;
34
33
  /** `e` with `o` applied (StateOverride.apply): opacity multiplies, the rest replaces. */
@@ -37,4 +36,6 @@ export declare function applyOverride(o: StateOverride, e: Element): Element;
37
36
  export declare function changesState(c: ClickAction | null): boolean;
38
37
  /** `state` after the click: the starting values (`initial`) back if it resets, then its `set` values, then each
39
38
  * `toggle` flipped (a missing variable starts false). */
40
- export declare function applyClick(c: ClickAction, state: Record<string, boolean>, initial?: Record<string, boolean>): Record<string, boolean>;
39
+ export declare function applyClick(c: ClickAction, state: Record<string, Var>, initial?: Record<string, Var>): Record<string, Var>;
40
+ /** A click's value given the state before it (ClickValue.value); an expression that can't be read leaves `current`. */
41
+ export declare function clickValue(v: ClickValue, state: Record<string, Var>, current: Var | undefined): Var;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phin-tech/dottie",
3
- "version": "0.10.0",
3
+ "version": "0.11.1",
4
4
  "description": "Render, play and compile Dottie (.dottie) drawings and animations in the browser",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -46,12 +46,14 @@
46
46
  "prepublishOnly": "npm test && node build.mjs"
47
47
  },
48
48
  "devDependencies": {
49
+ "@jitl/quickjs-singlefile-browser-release-sync": "^0.32.0",
49
50
  "@types/node": "^24.19.2",
50
51
  "@types/pngjs": "^6.0.5",
51
52
  "esbuild": "^0.25.12",
52
53
  "pixelmatch": "^7.2.0",
53
54
  "playwright": "^1.64.1",
54
55
  "pngjs": "^7.0.0",
56
+ "quickjs-emscripten-core": "^0.32.0",
55
57
  "typescript": "^5.9.3"
56
58
  },
57
59
  "publishConfig": {