diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index a391700de..023ba5d24 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -16,14 +16,16 @@ jobs: strategy: matrix: - node-version: [10.x, 12.x, 14.x] + node-version: [16.x, 20.x] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 + uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} - run: npm ci - - run: npm run build --if-present - - run: npm test + - run: npm run lint + - run: npm run test + env: + CI: true diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml deleted file mode 100644 index ef026cf1f..000000000 --- a/.github/workflows/nodejs.yml +++ /dev/null @@ -1,31 +0,0 @@ -# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node -# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions - -name: Node.js CI - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - -jobs: - build: - - runs-on: ubuntu-latest - - strategy: - matrix: - node-version: [10.x, 12.x, 13.x] - - steps: - - uses: actions/checkout@v2 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 - with: - node-version: ${{ matrix.node-version }} - - run: npm ci - - run: npm run lint - - run: npm run test-extensive - env: - CI: true diff --git a/.well-known/funding-manifest-urls b/.well-known/funding-manifest-urls new file mode 100644 index 000000000..bc204ec64 --- /dev/null +++ b/.well-known/funding-manifest-urls @@ -0,0 +1 @@ +https://yjs.dev diff --git a/INTERNALS.md b/INTERNALS.md index a7874076f..79bd7f29e 100644 --- a/INTERNALS.md +++ b/INTERNALS.md @@ -26,7 +26,7 @@ article](https://blog.kevinjahns.de/are-crdts-suitable-for-shared-editing/). Each client is assigned a unique *clientID* property on first insert. This is a random 53-bit integer (53 bits because that fits in the javascript safe integer -range). +range \[JavaScript uses IEEE 754 floats\]). ## List items @@ -60,7 +60,7 @@ characters have either been deleted or all characters are not deleted. The item will be split if the run is interrupted for any reason (eg a character in the middle of the run is deleted). -When an item is created, it stores a reference to the IDs of the preceeding and +When an item is created, it stores a reference to the IDs of the preceding and succeeding item. These are stored in the item's `origin` and `originRight` fields, respectively. These are used when peers concurrently insert at the same location in a document. Though quite rare in practice, Yjs needs to make sure @@ -88,7 +88,7 @@ When a local insert happens, Yjs needs to map the insert position in the document (eg position 1000) to an ID. With just the linked list, this would require a slow O(n) linear scan of the list. But when editing a document, most inserts are either at the same position as the last insert, or nearby. To -improve performance, Yjs stores a cache of the 10 most recently looked up +improve performance, Yjs stores a cache of the 80 most recently looked up insert positions in the document. This is consulted and updated when a position is looked up to improve performance in the average case. The cache is updated using a heuristic that is still changing (currently, it is updated when a new @@ -149,8 +149,8 @@ concepts that can be used to create a custom network protocol: * `update`: The Yjs document can be encoded to an *update* object that can be parsed to reconstruct the document. Also every change on the document fires -an incremental document updates that allows clients to sync with each other. -The update object is an Uint8Array that efficiently encodes `Item` objects and +an incremental document update that allows clients to sync with each other. +The update object is a Uint8Array that efficiently encodes `Item` objects and the delete set. * `state vector`: A state vector defines the known state of each user (a set of tuples `(client, clock)`). This object is also efficiently encoded as a diff --git a/LICENSE b/LICENSE index c8c5c6da7..f55e8b484 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,7 @@ The MIT License (MIT) -Copyright (c) 2014 - - Kevin Jahns . +Copyright (c) 2023 + - Kevin Jahns . - Chair of Computer Science 5 (Databases & Information Systems), RWTH Aachen University, Germany Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/README.md b/README.md index bcb18d1dc..15c1f2192 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ > A CRDT framework with a powerful abstraction of shared data -Yjs is a [CRDT implementation](#Yjs-CRDT-Algorithm) that exposes its internal +Yjs is a [CRDT implementation](#yjs-crdt-algorithm) that exposes its internal data structure as *shared types*. Shared types are common data types like `Map` or `Array` with superpowers: changes are automatically distributed to other peers and merged without merge conflicts. @@ -32,10 +32,34 @@ Otherwise you can find help on our community [discussion board](https://discuss. Please contribute to the project financially - especially if your company relies on Yjs. [![Become a Sponsor](https://img.shields.io/static/v1?label=Become%20a%20Sponsor&message=%E2%9D%A4&logo=GitHub&style=flat&color=d42f2d)](https://github.com/sponsors/dmonad) +## Professional Support + +* [Support Contract with the Maintainer](https://github.com/sponsors/dmonad) - +By contributing financially to the open-source Yjs project, you can receive +professional support directly from the author. This includes the opportunity for +weekly video calls to discuss your specific challenges. +* [Synergy Codes](https://synergycodes.com/yjs-services/) - Specializing in +consulting and developing real-time collaborative editing solutions for visual +apps, Synergy Codes focuses on interactive diagrams, complex graphs, charts, and +various data visualization types. Their expertise empowers developers to build +engaging and interactive visual experiences leveraging the power of Yjs. See +their work in action at [Visual Collaboration +Showcase](https://yjs-diagram.synergy.codes/). + ## Who is using Yjs -* [AFFiNE](https://affine.pro/) A local-first, privacy-first, open source knowledge base. 🏅 -* [Dynaboard](https://dynaboard.com/) Build web apps collaboratively. :star2: +* [AFFiNE](https://affine.pro/) A local-first, privacy-first, open source + knowledge base. :star2: +* [Huly](https://huly.io/) - Open Source All-in-One Project Management Platform :star2: +* [Cargo](https://cargo.site/) Site builder for designers and artists :star2: +* [Gitbook](https://gitbook.com) Knowledge management for technical teams :star2: +* [Evernote](https://evernote.com) Note-taking app :star2: +* [Lessonspace](https://thelessonspace.com) Enterprise platform for virtual + classrooms and online training :star2: +* [Ellipsus](ellipsus.com) - Collaborative writing app for storytelling etc. + Supports versioning, change attribution, and "blame". A solution for the whole + publishing process (also selling) :star: +* [Dynaboard](https://dynaboard.com/) Build web apps collaboratively. :star: * [Relm](https://www.relm.us/) A collaborative gameworld for teamwork and community. :star: * [Room.sh](https://room.sh/) A meeting application with integrated @@ -44,30 +68,86 @@ on Yjs. [![Become a Sponsor](https://img.shields.io/static/v1?label=Become%20a%2 Nimbus Web. :star: * [Pluxbox RadioManager](https://getradiomanager.com/) A web-based app to collaboratively organize radio broadcasts. :star: +* [modyfi](https://www.modyfi.com) - Modyfi is the design platform built for + multidisciplinary designers. Design, generate, animate, and more — without + switching between apps. :star: +* [Sana](https://sanalabs.com/) A learning platform with collaborative text + editing powered by Yjs. * [Serenity Notes](https://www.serenity.re/en/notes) End-to-end encrypted collaborative notes app. -* [PRSM](https://prsm.uk/) Collaborative mind-mapping and system visualisation. *[(source)](https://github.com/micrology/prsm)* +* [PRSM](https://prsm.uk/) Collaborative mind-mapping and system visualisation. + *[(source)](https://github.com/micrology/prsm)* * [Alldone](https://alldone.app/) A next-gen project management and collaboration platform. * [Living Spec](https://livingspec.com/) A modern way for product teams to collaborate. * [Slidebeamer](https://slidebeamer.com/) Presentation app. * [BlockSurvey](https://blocksurvey.io) End-to-end encryption for your forms/surveys. * [Skiff](https://skiff.org/) Private, decentralized workspace. +* [JupyterLab](https://jupyter.org/) Collaborative computational Notebooks +* [JupyterCad](https://jupytercad.readthedocs.io/en/latest/) Extension to + JupyterLab that enables collaborative editing of 3d FreeCAD Models. +* [JupyterGIS](https://github.com/geojupyter/jupytergis) Collaborative GIS + (Geographic Information System) editor in Jupyter +* [Hyperquery](https://hyperquery.ai/) A collaborative data workspace for + sharing analyses, documentation, spreadsheets, and dashboards. +* [Nosgestesclimat](https://nosgestesclimat.fr/groupe) The french carbon + footprint calculator has a group P2P mode based on yjs +* [oorja.io](https://oorja.io) Online meeting spaces extensible with + collaborative apps, end-to-end encrypted. +* [LegendKeeper](https://legendkeeper.com) Collaborative campaign planner and + worldbuilding app for tabletop RPGs. +* [IllumiDesk](https://illumidesk.com/) Build courses and content with A.I. +* [btw](https://www.btw.so) Open-source Medium alternative +* [AWS SageMaker](https://aws.amazon.com/sagemaker/) Tools for building Machine + Learning Models +* [linear](https://linear.app) Streamline issues, projects, and product roadmaps. +* [Arkiter](https://www.arkiter.com/) - Live interview software +* [Appflowy](https://www.appflowy.io/) - They use Yrs +* [Multi.app](https://multi.app) - Multiplayer app sharing: Point, draw and edit + in shared apps as if they're on your computer. They are using Yrs. +* [AppMaster](https://appmaster.io) A No-Code platform for creating + production-ready applications with source code generation. +* [Synthesia](https://www.synthesia.io) - Collaborative Video Editor +* [thinkdeli](https://thinkdeli.com) - A fast and simple notes app powered by AI +* [ourboard](https://github.com/raimohanska/ourboard) - A collaborative whiteboard + application +* [Ellie.ai](https://ellie.ai) - Data Product Design and Collaboration +* [GoPeer](https://gopeer.org/) - Collaborative tutoring +* [screen.garden](https://screen.garden) - Collaborative backend for PKM apps. +* [NextCloud](https://nextcloud.com/) - Content Collaboration Platform +* [keystatic](https://github.com/Thinkmill/keystatic) - git-based CMS +* [QDAcity](https://qdacity.com) - Collaborative qualitative data analysis platform +* [Kanbert](https://kanbert.com) - Project management software +* [Eclipse Theia](https://github.com/eclipse-theia/theia) - A cloud & desktop + IDE that runs in the browser. +* [ScienHub](https://scienhub.com) - Collaborative LaTeX editor in the browser. +* [Open Collaboration Tools](https://www.open-collab.tools/) - Collaborative +editing for your IDE or custom editor +* [Typst](https://typst.app/) - Compose, edit, and automate technical documents +* [Kedyou](https://kedyou.com/) - Digital workspaces for tutoring +* [Lightpage](https://lightpage.com/) - Personal living notebook +* [reearth-flow](https://github.com/reearth/reearth-flow) - + Collaboratively calculate and convert various data +* [ProtonMail | Proton Docs](https://proton.me/drive/docs) - E2E encrypted + collaborative documents in Proton Drive. +* [Theneo](https://www.theneo.io/) - AI-powered API docs with live team collaboration. ## Table of Contents -* [Overview](#Overview) - * [Bindings](#Bindings) - * [Providers](#Providers) -* [Getting Started](#Getting-Started) -* [API](#API) - * [Shared Types](#Shared-Types) - * [Y.Doc](#YDoc) - * [Document Updates](#Document-Updates) - * [Relative Positions](#Relative-Positions) - * [Y.UndoManager](#YUndoManager) -* [Yjs CRDT Algorithm](#Yjs-CRDT-Algorithm) -* [License and Author](#License-and-Author) +* [Overview](#overview) + * [Bindings](#bindings) + * [Providers](#providers) + * [Tooling](#tooling) + * [Ports](#ports) +* [Getting Started](#getting-started) +* [API](#api) + * [Shared Types](#shared-types) + * [Y.Doc](#ydoc) + * [Document Updates](#document-updates) + * [Relative Positions](#relative-positions) + * [Y.UndoManager](#yundomanager) +* [Yjs CRDT Algorithm](#yjs-crdt-algorithm) +* [License and Author](#license-and-author) ## Overview @@ -83,10 +163,42 @@ are implemented in separate modules. | [Quill](https://quilljs.com/) | ✔ | [y-quill](https://github.com/yjs/y-quill) | [demo](https://demos.yjs.dev/quill/quill.html) | | [CodeMirror](https://codemirror.net/) | ✔ | [y-codemirror](https://github.com/yjs/y-codemirror) | [demo](https://demos.yjs.dev/codemirror/codemirror.html) | | [Monaco](https://microsoft.github.io/monaco-editor/) | ✔ | [y-monaco](https://github.com/yjs/y-monaco) | [demo](https://demos.yjs.dev/monaco/monaco.html) | +| [Ace](https://ace.c9.io/) | ✔ | [y-ace](https://github.com/bajrangCoder/y-ace) | | | [Slate](https://github.com/ianstormtaylor/slate) | ✔ | [slate-yjs](https://github.com/bitphinix/slate-yjs) | [demo](https://bitphinix.github.io/slate-yjs-example) | +| [BlockSuite](https://github.com/toeverything/blocksuite) | ✔ | (native) | [demo](https://blocksuite-toeverything.vercel.app/?init) | +| [Lexical](https://lexical.dev/) | ✔ | (native) | [demo](https://lexical.dev/docs/collaboration/react#see-it-in-action) | +| [BlockNote](https://www.blocknotejs.org/docs/collaboration/real-time-collaboration) | ✔ | [y-prosemirror](https://github.com/yjs/y-prosemirror) | [demo](https://www.blocknotejs.org/docs/collaboration/real-time-collaboration) | +| [Tiptap](https://tiptap.dev/) | ✔ | [y-prosemirror](https://github.com/yjs/y-prosemirror) | [demo](https://template.tiptap.dev/preview/templates/simple) | +| [Milkdown](https://github.com/Milkdown/milkdown) | ✔ | [y-prosemirror](https://github.com/yjs/y-prosemirror) | [demo](https://milkdown.dev/playground) | +| [Superdoc](https://superdoc.dev/) | ✔ | (native) | [demo](https://superdoc.dev/) | | [valtio](https://github.com/pmndrs/valtio) | | [valtio-yjs](https://github.com/dai-shi/valtio-yjs) | [demo](https://codesandbox.io/s/valtio-yjs-demo-ox3iy) | | [immer](https://github.com/immerjs/immer) | | [immer-yjs](https://github.com/sep2/immer-yjs) | [demo](https://codesandbox.io/s/immer-yjs-demo-6e0znb) | +| React | | [react-yjs](https://github.com/nikgraf/react-yjs) | [demo](https://react-yjs-example.vercel.app/) | | React / Vue / Svelte / MobX | | [SyncedStore](https://syncedstore.org) | [demo](https://syncedstore.org/docs/react) | +| [mobx-keystone](https://mobx-keystone.js.org/) | | [mobx-keystone-yjs](https://github.com/xaviergonz/mobx-keystone/tree/master/packages/mobx-keystone-yjs) | [demo](https://mobx-keystone.js.org/examples/yjs-binding) | +| [PSPDFKit](https://www.nutrient.io/) | | [yjs-pspdfkit](https://github.com/hoangqwe159/yjs-pspdfkit) | [demo](https://github.com/hoangqwe159/yjs-pspdfkit) | +| [Rows n'Columns](https://www.rowsncolumns.app/) | ✔ | [@rowsncolumns/y-spreadsheet](https://docs.rowsncolumns.app/collaboration/yjs-collaboration) | | + +### Utilities + +Tools that extend the core functionality of Yjs. + +
+
y-utility
+
+Library with YMultiDocUndoManager (undo/redo across Yjs docs) and +YKeyValue (optimized key-value store). +
+
+ yjs-orderedtree 🌳 +
+
+Class for ordered trees via Y.Map. Handles insert, +delete, and move operations for folder-like +hierarchies. +
+ +
### Providers @@ -95,27 +207,65 @@ and storing shared data for offline usage is quite a hassle. **Providers** manage all that for you and are the perfect starting point for your collaborative app. +> This list of providers is incomplete. Please open PRs to add your providers to +> this list! + +#### Connection Providers +
+
y-websocket
+
+A module that contains a simple websocket backend and a websocket client that +connects to that backend. y-redis, +y-sweet, ypy-websocket, yrs-warp and +Hocuspocus (see below) are alternative +backends to y-websocket. +
y-webrtc
Propagates document updates peer-to-peer using WebRTC. The peers exchange -signaling data over signaling servers. Publically available signaling servers +signaling data over signaling servers. Publicly available signaling servers are available. Communication over the signaling servers can be encrypted by providing a shared secret, keeping the connection information and the shared document private.
-
y-websocket
+
@liveblocks/yjs 🌟
-A module that contains a simple websocket backend and a websocket client that -connects to that backend. The backend can be extended to persist updates in a -leveldb database. +Liveblocks Yjs provides a fully +hosted WebSocket infrastructure and persisted data store for Yjs +documents. No configuration or maintenance is required. It also features +Yjs webhook events, REST API to read and update Yjs documents, and a +browser DevTools extension.
-
y-indexeddb
+
y-sweet
-Efficiently persists document updates to the browsers indexeddb database. -The document is immediately available and only diffs need to be synced through the -network provider. +A standalone yjs server with persistence to S3 or filesystem. They offer a +cloud service as well. +
+
Hocuspocus
+
+A standalone extensible yjs server with sqlite persistence, webhooks, auth and more. +
+
@superviz/yjs
+
+ SuperViz Yjs Provider comes with a secure, scalable real-time infrastructure + for Yjs documents, fully compatible with a set of real-time + collaboration components offered by SuperViz. This solution ensures + synchronization, offline editing, and real-time updates, enabling + multiple users to collaborate effectively within shared workspaces. +
+
PartyKit
+
+Cloud service for building multiplayer apps. +
+
@pluv/crdt-yjs
+
+Use pluv.io as a +full-featured backend for Yjs. pluv.io can either be be used on its +fully-managed WebSocket infrastructure, or self-hosted on Cloudflare Workers +and Node.js runtimes. Offers a typesafe API with authentication, webhooks, +rooms, and more.
y-libp2p
Uses libp2p to propagate updates via @@ -137,9 +287,106 @@ Use Matrix as transport and storage of Yjs updates, so you can focus building your client app and Matrix can provide powerful features like Authentication, Authorization, Federation, hosting (self-hosting or SaaS) and even End-to-End Encryption (E2EE). -
+ +
yrb-actioncable
+
+An ActionCable companion for Yjs clients. There is a fitting +redis extension as well. +
+
ypy-websocket
+
+Websocket backend, written in Python. +
+
Tinybase
+
+The reactive data store for local-first apps. They support multiple CRDTs and + different network technologies. +
+
y-webxdc
+
+Provider for sharing data in webxdc chat apps. +
+
secsync
+
+An architecture to relay end-to-end encrypted CRDTs over a central service. +
+
y-electric
+
+ Sync Yjs over ElectricSQL. +
+
yjs-cf-ws-provider
+
+ Cloudflare provider for Yjs based on durable objects. +
+
nostr-crdt
+
+ Sync Yjs over nostr. +
+#### Persistence Providers + +
+
y-indexeddb
+
+Efficiently persists document updates to the browsers indexeddb database. +The document is immediately available and only diffs need to be synced through the +network provider. +
+
y-mongodb-provider
+
+Adds persistent storage to a server with MongoDB. Can be used with the +y-websocket provider. +
+
y-fire
+
+A database and connection provider for Yjs based on Firestore. +
+
y-op-sqlite
+
+ Persist YJS updates in your React Native app using + op-sqlite + , the fastest SQLite library for React Native. +
+
y-postgresql
+
+ Provides persistent storage for a web server using PostgreSQL and + is easily compatible with y-websocket. +
+
k_yrs_go
+
+ Golang database server for YJS CRDT using Postgres + Redis +
+
y-op-sqlite
+
+ Yjs persistence provider for op-sqlite +
+ +
+ +### Tooling + +* [y-sweet debugger](https://y-sweet.cloud/advanced/debugger) +* [liveblocks devtools](https://liveblocks.io/devtools) +* [Yjs inspector](https://inspector.yjs.dev) + +### Ports + +There are several Yjs-compatible ports to other programming languages. + +* [y-octo](https://github.com/toeverything/y-octo) - Rust implementation by +[AFFiNE](https://affine.pro) +* [y-crdt](https://github.com/y-crdt/y-crdt) - Rust implementation with multiple +language bindings to other languages + * [yrs](https://github.com/y-crdt/y-crdt/tree/main/yrs) - Rust interface + * [ypy](https://github.com/y-crdt/ypy) - Python binding + * [yrb](https://github.com/y-crdt/yrb) - Ruby binding + * [yswift](https://github.com/y-crdt/yswift) - Swift binding + * [yffi](https://github.com/y-crdt/y-crdt/tree/main/yffi) - C-FFI + * [ywasm](https://github.com/y-crdt/y-crdt/tree/main/ywasm) - WASM binding + * [y_ex](https://github.com/satoren/y_ex) - Elixir bindings +* [ycs](https://github.com/yjs/ycs) - .Net compatible C# implementation. + ## Getting Started Install Yjs and a provider with your favorite package manager: @@ -151,12 +398,15 @@ npm i yjs y-websocket Start the y-websocket server: ```sh -PORT=1234 node ./node_modules/y-websocket/bin/server.js +PORT=1234 node ./node_modules/y-websocket/bin/server.cjs ``` ### Example: Observe types ```js +import * as Y from 'yjs'; + +const doc = new Y.Doc(); const yarray = doc.getArray('my-array') yarray.observe(event => { console.log('yarray was modified') @@ -249,6 +499,11 @@ necessary.

const yarray = new Y.Array()
+ +Y.Array.from(Array<object|boolean|Array|string|number|null|Uint8Array|Y.Type>): +Y.Array + +
An alternative factory function to create a Y.Array based on existing content.
parent:Y.AbstractType|null
insert(index:number, content:Array<object|boolean|Array|string|number|null|Uint8Array|Y.Type>) @@ -278,6 +533,11 @@ forEach(function(value:object|boolean|Array|string|number|null|Uint8Array|Y.Type
map(function(T, number, YArray):M):Array<M>
+ clone(): Y.Array +
+Clone all values into a fresh Y.Array instance. The returned type can be +included into the Yjs document. +
toArray():Array<object|boolean|Array|string|number|null|Uint8Array|Y.Type>
Copies the content of this YArray to a new Array.
toJSON():Array<Object|boolean|Array|string|number|null> @@ -334,8 +594,6 @@ or any of its children.
has(key:string):boolean
- get(index:number) -
clear()
Removes all elements from this YMap.
clone():Y.Map @@ -485,8 +743,6 @@ or any of its children.
Clone this type into a fresh Yjs type.
toArray():Array<Y.XmlElement|Y.XmlText>
Copies the children to a new Array.
- toDOM():DocumentFragment -
Transforms this type and all children to new DOM elements.
toString():string
Get the XML serialization of all descendants.
toJSON():string @@ -560,8 +816,6 @@ content and be actually XML compliant.
Clone this type into a fresh Yjs type.
toArray():Array<Y.XmlElement|Y.XmlText>
Copies the children to a new Array.
- toDOM():Element -
Transforms this type and all children to a new DOM element.
toString():string
Get the XML serialization of all descendants.
toJSON():string @@ -632,6 +886,8 @@ type. Doesn't log types that have not been defined (using
Define a shared Y.Map type. Is equivalent to y.get(string, Y.Map).
getText(string):Y.Text
Define a shared Y.Text type. Is equivalent to y.get(string, Y.Text).
+ getXmlElement(string, string):Y.XmlElement +
Define a shared Y.XmlElement type. Is equivalent to y.get(string, Y.XmlElement).
getXmlFragment(string):Y.XmlFragment
Define a shared Y.XmlFragment type. Is equivalent to y.get(string, Y.XmlFragment).
on(string, function) @@ -646,7 +902,8 @@ type. Doesn't log types that have not been defined (using on('update', function(updateMessage:Uint8Array, origin:any, Y.Doc):void)
Listen to document updates. Document updates must be transmitted to all other -peers. You can apply document updates in any order and multiple times. +peers. You can apply document updates in any order and multiple times. Use `updateV2` +to receive V2 events.
on('beforeTransaction', function(Y.Transaction, Y.Doc):void)
Emitted before each transaction.
@@ -686,7 +943,7 @@ doc1.getArray('myarray').insert(0, ['Hello doc2, you got this?']) doc2.getArray('myarray').get(0) // => 'Hello doc2, you got this?' ``` -Yjs internally maintains a [state vector](#State-Vector) that denotes the next +Yjs internally maintains a [state vector](#state-vector) that denotes the next expected clock from each client. In a different interpretation it holds the number of structs created by each client. When two clients sync, you can either exchange the complete document structure or only the differences by sending the @@ -738,17 +995,43 @@ const diff2 = Y.diffUpdate(currentState2, stateVector1) // sync clients currentState1 = Y.mergeUpdates([currentState1, diff2]) -currentState1 = Y.mergeUpdates([currentState1, diff1]) +currentState2 = Y.mergeUpdates([currentState2, diff1]) +``` + +#### Obfuscating Updates + +If one of your users runs into a weird bug (e.g. the rich-text editor throws +error messages), then you don't have to request the full document from your +user. Instead, they can obfuscate the document (i.e. replace the content with +meaningless generated content) before sending it to you. Note that someone might +still deduce the type of content by looking at the general structure of the +document. But this is much better than requesting the original document. + +Obfuscated updates contain all the CRDT-related data that is required for +merging. So it is safe to merge obfuscated updates. + +```javascript +const ydoc = new Y.Doc() +// perform some changes.. +ydoc.getText().insert(0, 'hello world') +const update = Y.encodeStateAsUpdate(ydoc) +// the below update contains scrambled data +const obfuscatedUpdate = Y.obfuscateUpdate(update) +const ydoc2 = new Y.Doc() +Y.applyUpdate(ydoc2, obfuscatedUpdate) +ydoc2.getText().toString() // => "00000000000" ``` #### Using V2 update format Yjs implements two update formats. By default you are using the V1 update format. -You can opt-in into the V2 update format wich provides much better compression. +You can opt-in into the V2 update format which provides much better compression. It is not yet used by all providers. However, you can already use it if you are building your own provider. All below functions are available with the -suffix "V2". E.g. `Y.applyUpdate` ⇒ `Y.applyUpdateV2`. We also support conversion -functions between both formats: `Y.convertUpdateFormatV1ToV2` & `Y.convertUpdateFormatV2ToV1`. +suffix "V2". E.g. `Y.applyUpdate` ⇒ `Y.applyUpdateV2`. Also when listening to updates +you need to specifically need listen for V2 events e.g. `yDoc.on('updateV2', …)`. +We also support conversion functions between both formats: +`Y.convertUpdateFormatV1ToV2` & `Y.convertUpdateFormatV2ToV1`. #### Update API @@ -873,7 +1156,7 @@ encoding format for document updates. If you prefer JSON encoding, you can simply JSON.stringify / JSON.parse the relative position instead. Y.decodeRelativePosition(Uint8Array):RelativePosition -
Decode a binary-encoded relative position to a RelativePositon object.
+
Decode a binary-encoded relative position to a RelativePosition object.
### Y.UndoManager @@ -993,7 +1276,7 @@ doc.transact(() => { ytext.insert(0, 'abc') }, 41) undoManager.undo() -ytext.toString() // => '' (not tracked because 41 is not an instance of +ytext.toString() // => 'abc' (not tracked because 41 is not an instance of // `trackedTransactionorigins`) ytext.delete(0, 3) // revert change @@ -1051,16 +1334,17 @@ More information about the specific implementation is available in [INTERNALS.md](./INTERNALS.md) and in [this walkthrough of the Yjs codebase](https://youtu.be/0l5XgnQ6rB4). -CRDTs that suitable for shared text editing suffer from the fact that they only grow -in size. There are CRDTs that do not grow in size, but they do not have the -characteristics that are benificial for shared text editing (like intention -preservation). Yjs implements many improvements to the original algorithm that -diminish the trade-off that the document only grows in size. We can't garbage -collect deleted structs (tombstones) while ensuring a unique order of the -structs. But we can 1. merge preceeding structs into a single struct to reduce -the amount of meta information, 2. we can delete content from the struct if it -is deleted, and 3. we can garbage collect tombstones if we don't care about the -order of the structs anymore (e.g. if the parent was deleted). +CRDTs that are suitable for shared text editing suffer from the fact that they +only grow in size. There are CRDTs that do not grow in size, but they do not +have the characteristics that are beneficial for shared text editing (like +intention preservation). Yjs implements many improvements to the original +algorithm that diminish the trade-off that the document only grows in size. We +can't garbage collect deleted structs (tombstones) while ensuring a unique +order of the structs. But we can 1. merge preceding structs into a single +struct to reduce the amount of meta information, 2. we can delete content from +the struct if it is deleted, and 3. we can garbage collect tombstones if we +don't care about the order of the structs anymore (e.g. if the parent was +deleted). **Examples:** @@ -1097,6 +1381,15 @@ But we use state vectors only to describe the state of the local document, so we can compute the missing struct of the remote client. We do not use it to track causality. +### Formal Proof + +[lean-yjs](https://github.com/iasakura/lean-yjs) provides a formal verification +of the YATA CRDT algorithm that Yjs implements, using the Lean theorem prover to +mathematically prove correctness properties. While the CRDT algorithm itself is +correct (currently proven for preservation and commutativity), the project +reveals that [the pseudocode in the original YATA paper contains +errors](https://discuss.yjs.dev/t/lean-yjs-formally-proving-the-yjs-conflict-resolution-algorithms/3875/2). + ## License and Author Yjs and all related projects are [**MIT licensed**](./LICENSE). diff --git a/attributing-content.md b/attributing-content.md new file mode 100644 index 000000000..72783e287 --- /dev/null +++ b/attributing-content.md @@ -0,0 +1,121 @@ +# IdSets and IdMaps + +`IdSet` is a data structure (formerly `DeleteSet`) that allows us to efficiently +represent ranges of ids in Yjs (all content is identifyable by ids). + +`IdMap` is a new data structure that allows us to efficiently map ids to +attributes. It can be efficiently encoded. + +We can perform all usual set operations on `IdMap`s and `IdSet`s: diff, merge, +intersect. + +# Attribution of content + +In order to implement a Google Docs-like versioning feature, we want to be able +to attribute content with additional information (who created the change, +when was this change created, ..). + +When we click on a version in Google Docs, we might get annotated changes like +this: + +``` +# E.g. If Bob appends "world" to the previous version "hello " +[{ insert: 'hello' }, { insert: 'world', color: 'blue', creator: 'Bob', when: 'yesterday' }] +# E.g. If Bob deletes "world" from the previous version "hello world" +[{ insert: 'hello' }, { insert: 'world', backgroundColor: 'red', creator: 'Bob', when: 'yesterday' }] +``` + +In Yjs, we can now "attribute" changes with additional information. When we +render content using methods like `toString()` or `getDelta()`, Yjs will render +the unattributed content as-is, but it will render the attributed content with +the additional information. As all changes in Yjs are identifyable by Ids, we +can use `IdMap`s to map changes to "attributions". For example, we could +attribute deletions and insertions of a change and render them: + +```js +// We create some initial content "Hello World!". Then we create another +// document that will have a bunch of changes (make "Hell" italic, replace "World" +// with "Attribution"). +const ydocVersion0 = new Y.Doc({ gc: false }) +ydocVersion0.getText().insert(0, 'Hello World!') +const ydoc = new Y.Doc({ gc: false }) +Y.applyUpdate(ydoc, Y.encodeStateAsUpdate(ydocVersion0)) +const ytext = ydoc.getText() +ytext.applyDelta([{ retain: 4, attributes: { italic: true } }, { retain: 2 }, { delete: 5 }, { insert: 'attributions' }]) +// this represents to all insertions of ydoc +const insertionSet = Y.createInsertionSetFromStructStore(ydoc.store) +const deleteSet = Y.createDeleteSetFromStructStore(ydoc.store) +// exclude the changes from `ydocVersion0` +const insertionSetDiff = Y.diffIdSet(insertionSet, Y.createInsertionSetFromStructStore(ydocVersion0.store)) +const deleteSetDiff = Y.diffIdSet(deleteSet, Y.createDeleteSetFromStructStore(ydocVersion0.store)) +// assign attributes to the diff +const attributedInsertions = createIdMapFromIdSet(insertionSetDiff, [new Y.Attribution('insert', 'Bob')]) +const attributedDeletions = createIdMapFromIdSet(deleteSetDiff, [new Y.Attribution('delete', 'Bob')]) +// now we can define an attribution manager that maps these changes to output. One of the +// implementations is the TwosetAttributionManager +const attributionManager = new TwosetAttributionManager(attributedInsertions, attributedDeletions) +// we render the attributed content with the attributionManager +let attributedContent = ytext.getContent(attributionManager) +console.log(JSON.stringify(attributedContent.toJSON().ops, null, 2)) +let expectedContent = delta.create().insert('Hell', { italic: true }, { attributes: { italic: ['Bob'] } }).insert('o ').insert('World', {}, { delete: ['Bob'] }).insert('attributions', {}, { insert: ['Bob'] }).insert('!') +t.assert(attributedContent.equals(expectedContent)) + +// this is how the output would look like +const output = [ + { + "insert": "Hell", + "attributes": { + "italic": true + }, + "attribution": { // no "insert" attribution: the insertion "Hell" is not attributed to anyone + "attributes": { + "italic": [ // the attribute "italic" was added by Bob + "Bob" + ] + } + } + }, + { + "insert": "o " // the insertion "o " has no attributions + }, + { + "insert": "World", + "attribution": { // the insertion "World" was deleted by Bob + "delete": [ + "Bob" + ] + } + }, + { + "insert": "attributions", // the insertion "attributions" was inserted by Bob + "attribution": { + "insert": [ + "Bob" + ] + } + }, + { + "insert": "!" // the insertion "!" has no attributions + } +] +``` + +We get a similar output to Google Docs: Insertions, Deletions, and changes to +formatting (attributes) are clearly associated to users. It will be the job of +the editor to render those changes with background-color etc.. + +Of course, we could associated changes also to multiple users like this: + +```js +const attributedDeletions = createIdMapFromIdSet(deleteSetDiff, [new Y.Attribution('insert', 'Bob'), new Y.Attribution('insert', 'OpenAI o3')]) +``` + +You could use the same output to calculate a real diff as well (consisting of +deletions and insertions only, without Attributions). + +`AttributionManager` is an abstract class for mapping attributions. It is +possible to highlight arbitrary content with this approach. + +The AttributionManager is encodes very efficiently. The ids are encoded using +run-length encoding and the Attributes are de-duplicated and only encoded once. +The above example encodes in 20 bytes. diff --git a/attribution-manager.md b/attribution-manager.md new file mode 100644 index 000000000..a90c6b1db --- /dev/null +++ b/attribution-manager.md @@ -0,0 +1,237 @@ + +# Attribution Feature + +The Attribution feature extends Yjs types to provide rich metadata about content +changes, including information about who created, deleted, or formatted content. +This enables powerful collaborative editing features such as authorship tracking +and change visualization. The information about who performed which changes can +be handled by a separate CRDT (which is part of the attribution manager). + +## Core Concepts + +### Attribution Manager + +The `attributionManager` is the central component that tracks and manages +attribution data. It must be passed to methods that support attribution to +enable the feature. + +Different implementations of AttributionManager are available for different use cases: +- `DiffingAttributionManager`: Highlights the differences between two Yjs documents +- `SnapshotAttributionManager`: Highlights the differences between two snapshots + +### Attributed Content + +Attributed content includes standard Yjs operations enhanced with attribution metadata: + +```javascript +// Standard content +[{ insert: 'hello world' }] + +// Attributed content +[ + { insert: 'hello', attribution: { insert: ['kevin'] } }, + { insert: ' world', attribution: { insert: ['alice'] } } +] +``` + +### Delete Attribution + +Deleted content is represented in attributed results to maintain authorship information and proper position tracking: + +```javascript +// Shows deleted content with attribution +[ + { insert: 'hello ', attribution: { delete: ['kevin'] } }, + { insert: 'world' } +] +``` + +## API Reference + +### YText + +#### `getDelta([attributionManager])` + +Returns the delta representation of the YText content, optionally with attribution information. + +**Parameters:** +- `attributionManager` (optional): The attribution manager instance + +**Returns:** +- Array of delta operations, with attribution metadata if `attributionManager` is provided + +**Examples:** + +```javascript +const ytext = new Y.Text() +// Content is inserted during collaborative editing +// Attribution is handled automatically by the server + +// Without attribution +const delta = ytext.getDelta() +// [{ insert: 'hello world' }] + +// With attribution +const attributedDelta = ytext.getDelta(attributionManager) +// [ +// { insert: 'hello', attribution: { insert: ['kevin'] } }, +// { insert: ' world', attribution: { insert: ['alice'] } } +// ] +``` + +#### `getContent([attributionManager])` + +Returns the content representation with optional attribution information. + +**Parameters:** +- `attributionManager` (optional): The attribution manager instance + +**Returns:** +- Content representation with attribution metadata if `attributionManager` is provided + +### YArray + +#### `getContent([attributionManager])` + +Returns the array content with optional attribution information for each element. + +**Parameters:** +- `attributionManager` (optional): The attribution manager instance + +**Returns:** +- Array content with attribution metadata if `attributionManager` is provided + +### YMap + +#### `getContent([attributionManager])` + +Returns the map content with optional attribution information for each key-value pair. + +**Parameters:** +- `attributionManager` (optional): The attribution manager instance + +**Returns:** +- Map content with attribution metadata if `attributionManager` is provided + +## Position Adjustments + +When working with attributed content, position calculations must account for deleted content that appears in the attributed representation but not in the standard representation. + +### Example: Position Adjustment + +```javascript +// Standard content (length: 5) +ytext.toString() // "world" + +// Attributed content (includes deleted content) +ytext.getDelta(attributionManager) +// [ +// { insert: 'hello ', attribution: { delete: ['kevin'] } }, // positions 0-5 +// { insert: 'world' } // positions 6-10 +// ] + +// To insert after "world": +// - Standard position: 5 (after "world") +// - Attributed position: 11 (after "world" accounting for deleted "hello ") +``` + +## Use Cases + +Events in Yjs are enhanced to work with attributed content, automatically adjusting positions when attribution is considered. + +### Event Position Adjustment + +When an `attributionManager` is used, event positions are automatically adjusted to account for deleted content. + +**Example:** + +```javascript +// Initial content: "hello world" +// User deletes "hello " (positions 0-6) +// Current visible content: "world" + +ytext.observe((event, transaction) => { + // User wants to insert "!" after "world" + + // Standard event (without attribution) + const standardDelta = event.getDelta() + // Shows insertion at position 5 (after "world" in visible content) + + // Attributed event (with attribution manager) + const attributedDelta = event.getDelta(attributionManager) + // Shows insertion at position 11 (accounting for deleted "hello ") + // [ + // { insert: 'hello ', attribution: { delete: ['kevin'] } }, + // { insert: 'world' }, + // { insert: '!' } // inserted at attributed position 11 + // ] +}) +``` + +## Use Cases + +### Authorship Visualization + +Display content with visual indicators of who created each part: + +```javascript +function renderWithAuthorship(ytext, attributionManager) { + const attributedDelta = ytext.getDelta(attributionManager) + + return attributedDelta.map(op => { + const author = op.attribution?.insert?.[0] || 'unknown' + const isDeleted = op.attribution?.delete + + return { + content: op.insert, + author, + isDeleted, + className: `author-${author} ${isDeleted ? 'deleted' : ''}` + } + }) +} +``` + +### Change Tracking + +Track who made specific changes to content: + +```javascript +function trackChanges(ytext, attributionManager) { + ytext.observe((event, transaction) => { + const changes = event.changes.getAttributedDelta?.(attributionManager) || event.changes.delta + + changes.forEach(change => { + if (change.attribution) { + console.log(`Change by ${change.attribution.insert?.[0] || change.attribution.delete?.[0]}:`, change) + } + }) + }) +} +``` + +## Best Practices + +### Attribution Manager Lifecycle + +- Create one attribution manager per document or collaboration session +- Ensure the attribution manager is consistently used across all operations +- Pass the same attribution manager instance to all methods that need attribution + +## Migration Guide + +### Upgrading Existing Code + +To add attribution support to existing Yjs applications: + +1. **Add attribution manager**: Create and configure an attribution manager +2. **Update method calls**: Add the attribution manager parameter to relevant method calls +3. **Handle attributed content**: Update code to handle the new attribution metadata format +4. **Adjust position calculations**: Update position calculations to account for deleted content + +### Backward Compatibility + +The Attribution feature is fully backward compatible: +- All existing methods work without the attribution manager parameter +- Existing code continues to work unchanged +- Attribution is opt-in and doesn't affect performance when not used diff --git a/funding.json b/funding.json new file mode 100644 index 000000000..676c306e1 --- /dev/null +++ b/funding.json @@ -0,0 +1,148 @@ +{ + "version": "v1.0.0", + "entity": { + "type": "group", + "role": "steward", + "name": "Kevin Jahns", + "email": "kevin.jahns@protonmail.com", + "phone": "", + "description": "Independent OSS Developer maintaining Yjs and many related libraries. My goal is to make the web more (real-time) collaborative.", + "webpageUrl": { + "url": "https://yjs.dev", + "wellKnown": "https://yjs.dev/.well-known/funding-manifest-urls" + } + }, + "projects": [ + { + "guid": "yjs", + "name": "Yjs", + "description": "A library for building collaborative applications. #p2p #local-first #CRDT Funding this project will also enable me to maintain the other Yjs-related technologies.", + "webpageUrl": { + "url": "https://yjs.dev", + "wellKnown": "https://yjs.dev/.well-known/funding-manifest-urls" + }, + "repositoryUrl": { + "url": "https://github.com/yjs/yjs" + }, + "licenses": [ + "spdx:MIT" + ], + "tags": [ + "collaboration", + "p2p", + "CRDT", + "rich-text", + "real-time" + ] + }, + { + "guid": "titanic", + "name": "Titanic", + "description": "A provider for syncing millions of docs efficiently with other peers. This will become the foundation for building real local-first apps with Yjs.", + "webpageUrl": { + "url": "https://github.com/yjs/titanic", + "wellKnown": "https://github.com/yjs/titanic/blob/main/.well-known/funding-manifest-urls" + }, + "repositoryUrl": { + "url": "https://github.com/yjs/titanic", + "wellKnown": "https://github.com/yjs/titanic/blob/main/.well-known/funding-manifest-urls" + }, + "licenses": [ + "spdx:MIT" + ], + "tags": [ + "privacy", + "collaboration", + "p2p", + "CRDT", + "rich-text", + "real-time", + "web-development" + ] + } + ], + "funding": { + "channels": [ + { + "guid": "github-sponsors", + "type": "payment-provider", + "address": "https://github.com/sponsors/dmonad", + "description": "For funding of the Yjs project" + }, + { + "guid": "yjs-opencollective", + "type": "payment-provider", + "address": "https://opencollective.com/y-collective/projects/yjs", + "description": "For funding Yjs via the OpenCollective." + } + ], + "plans": [ + { + "guid": "supporter", + "status": "active", + "name": "Supporter", + "description": "", + "amount": 0, + "currency": "USD", + "frequency": "monthly", + "channels": [ + "github-sponsors", + "yjs-opencollective" + ] + }, + { + "guid": "titanic-funding", + "status": "active", + "name": "Titanic Funding", + "description": "Fund the next generation of local-first providers.", + "amount": 30000, + "currency": "USD", + "frequency": "one-time", + "channels": [ + "github-sponsors", + "yjs-opencollective" + ] + }, + { + "guid": "bronze-sponsor", + "status": "active", + "name": "Bronze Sponsor", + "description": "This is the recommended plan for companies that use Yjs.", + "amount": 500, + "currency": "USD", + "frequency": "monthly", + "channels": [ + "github-sponsors", + "yjs-opencollective" + ] + }, + { + "guid": "silver-sponsor", + "status": "active", + "name": "Silver Sponsor", + "description": "This is the recommended plan for large/successful companies that use Yjs.", + "amount": 1000, + "currency": "USD", + "frequency": "monthly", + "channels": [ + "github-sponsors", + "yjs-opencollective" + ] + }, + { + "guid": "gold-sponsor", + "status": "active", + "name": "Gold Sponsor", + "description": "This is the recommended plan for successful companies that build their entire product around Yjs-related technologies.", + "amount": 3000, + "currency": "USD", + "frequency": "monthly", + "channels": [ + "github-sponsors", + "yjs-opencollective" + ] + } + ], + "history": null + } +} diff --git a/package-lock.json b/package-lock.json index 69ea9528f..f09baa48c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,844 +1,778 @@ { "name": "yjs", - "version": "13.5.41", - "lockfileVersion": 2, + "version": "14.0.0-10", + "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "yjs", - "version": "13.5.41", + "version": "14.0.0-10", "license": "MIT", "dependencies": { - "lib0": "^0.2.49" + "lib0": "^0.2.115-2" }, "devDependencies": { - "@rollup/plugin-commonjs": "^17.0.0", - "@rollup/plugin-node-resolve": "^11.2.1", - "concurrently": "^3.6.1", - "http-server": "^0.12.3", - "jsdoc": "^3.6.7", - "markdownlint-cli": "^0.23.2", - "rollup": "^2.60.0", - "standard": "^16.0.4", - "tui-jsdoc-template": "^1.2.2", - "typescript": "^4.4.4", - "y-protocols": "^1.0.5" + "@types/node": "^22.14.1", + "@y/protocols": "^1.0.6-1", + "concurrently": "^9.2.1", + "markdownlint-cli": "^0.45.0", + "rollup": "^4.52.5", + "standard": "^17.1.2", + "typescript": "^5.9.3" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=8.0.0" }, "funding": { "type": "GitHub Sponsors ❤", "url": "https://github.com/sponsors/dmonad" } }, - "node_modules/@babel/code-frame": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", - "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/highlight": "^7.16.7" + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.17.9", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.17.9.tgz", - "integrity": "sha512-J9PfEKCbFIv2X5bjTMiZu6Vf341N05QIY+d6FvVKynkG1S7G0j3I0QoRtWIrXhZ+/Nlb5Q0MzqL7TokEJ5BNHg==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.16.7", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, - "engines": { - "node": ">=6.9.0" + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "node_modules/@babel/parser": { - "version": "7.17.9", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.9.tgz", - "integrity": "sha512-vqUSBLP8dQHFPdPi9bc5GK9vRkYHJ49fsZdtoJ8EQ8ibpwk5rPKfvNIwChB0KVXcIjcepEBBd2VHC5r9Gy8ueg==", + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, - "bin": { - "parser": "bin/babel-parser.js" - }, + "license": "MIT", "engines": { - "node": ">=6.0.0" + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/eslintrc": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.3.0.tgz", - "integrity": "sha512-1JTKgrOKAHVivSvOYw+sJOunkBjUOvjqWk1DPja7ZFhIS2mX/4EgTT8M7eTK9jrKhL/FvXXEbQwIs3pg1xp3dg==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, + "license": "MIT", "dependencies": { "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^12.1.0", - "ignore": "^4.0.6", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "lodash": "^4.17.20", - "minimatch": "^3.0.4", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/@eslint/eslintrc/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/@eslint/eslintrc/node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } }, - "node_modules/@eslint/eslintrc/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/@rollup/plugin-commonjs": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-17.1.0.tgz", - "integrity": "sha512-PoMdXCw0ZyvjpCMT5aV4nkL0QywxP29sODQsSGeDpr/oI49Qq9tRtAsb/LbYbDzFlOydVEqHmmZWFtXJEAX9ew==", + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { - "@rollup/pluginutils": "^3.1.0", - "commondir": "^1.0.1", - "estree-walker": "^2.0.1", - "glob": "^7.1.6", - "is-reference": "^1.2.1", - "magic-string": "^0.25.7", - "resolve": "^1.17.0" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">= 8.0.0" - }, - "peerDependencies": { - "rollup": "^2.30.0" + "node": "*" } }, - "node_modules/@rollup/plugin-node-resolve": { - "version": "11.2.1", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.2.1.tgz", - "integrity": "sha512-yc2n43jcqVyGE2sqV5/YCmocy9ArjVAP/BeXyTtADTBBX6V0e5UMqwO8CdQ0kzjb6zu5P1qMzsScCMRvE9OlVg==", + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", "dev": true, - "dependencies": { - "@rollup/pluginutils": "^3.1.0", - "@types/resolve": "1.17.1", - "builtin-modules": "^3.1.0", - "deepmerge": "^4.2.2", - "is-module": "^1.0.0", - "resolve": "^1.19.0" - }, + "license": "MIT", "engines": { - "node": ">= 10.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@rollup/pluginutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", - "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@types/estree": "0.0.39", - "estree-walker": "^1.0.1", - "picomatch": "^2.2.2" + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" }, "engines": { - "node": ">= 8.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0" - } - }, - "node_modules/@rollup/pluginutils/node_modules/estree-walker": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", - "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", - "dev": true - }, - "node_modules/@types/estree": { - "version": "0.0.39", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", - "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", - "dev": true - }, - "node_modules/@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", - "dev": true - }, - "node_modules/@types/linkify-it": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.2.tgz", - "integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==", - "dev": true - }, - "node_modules/@types/markdown-it": { - "version": "12.2.3", - "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", - "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", - "dev": true, - "dependencies": { - "@types/linkify-it": "*", - "@types/mdurl": "*" + "node": ">=10.10.0" } }, - "node_modules/@types/mdurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", - "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==", - "dev": true - }, - "node_modules/@types/node": { - "version": "17.0.25", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.25.tgz", - "integrity": "sha512-wANk6fBrUwdpY4isjWrKTufkrXdu1D2YHCot2fD/DfWxF5sMrVSA+KN7ydckvaTCh0HiqX9IVl0L5/ZoXg5M7w==", - "dev": true - }, - "node_modules/@types/resolve": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", - "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==", + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { - "@types/node": "*" - } - }, - "node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + "node": "*" } }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" }, "funding": { "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", "dev": true, - "engines": { - "node": ">=6" - } + "license": "BSD-3-Clause" }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": "20 || >=22" } }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", "dev": true, + "license": "MIT", "dependencies": { - "color-convert": "^1.9.0" + "@isaacs/balanced-match": "^4.0.1" }, "engines": { - "node": ">=4" + "node": "20 || >=22" } }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/array-includes": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.4.tgz", - "integrity": "sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw==", + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, + "license": "ISC", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1", - "get-intrinsic": "^1.1.1", - "is-string": "^1.0.7" + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=12" } }, - "node_modules/array.prototype.flat": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz", - "integrity": "sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw==", + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.2", - "es-shim-unscopables": "^1.0.0" + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 8" } }, - "node_modules/array.prototype.flatmap": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.0.tgz", - "integrity": "sha512-PZC9/8TKAIxcWKdyeb77EzULHPrIX/tIZebLJUQOMR1OwYosT8yggdfWScfTBCDj5utONvOuPQQumYsU2ULbkg==", + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.2", - "es-shim-unscopables": "^1.0.0" - }, + "license": "MIT", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 8" } }, - "node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, "engines": { - "node": ">=8" + "node": ">= 8" } }, - "node_modules/async": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.5.tgz", + "integrity": "sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==", + "cpu": [ + "arm" + ], "dev": true, - "dependencies": { - "lodash": "^4.17.14" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "license": "MIT", + "optional": true, + "os": [ + "android" + ] }, - "node_modules/basic-auth": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.1.0.tgz", - "integrity": "sha1-RSIe5Cn37h5QNb4/UVM/HN/SmIQ=", + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.5.tgz", + "integrity": "sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==", + "cpu": [ + "arm64" + ], "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "dev": true - }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", - "dev": true + "license": "MIT", + "optional": true, + "os": [ + "android" + ] }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.5.tgz", + "integrity": "sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/builtin-modules": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz", - "integrity": "sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==", + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.5.tgz", + "integrity": "sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==", + "cpu": [ + "x64" + ], "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.5.tgz", + "integrity": "sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.5.tgz", + "integrity": "sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==", + "cpu": [ + "x64" + ], "dev": true, - "engines": { - "node": ">=6" - } + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] }, - "node_modules/catharsis": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", - "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.5.tgz", + "integrity": "sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==", + "cpu": [ + "arm" + ], "dev": true, - "dependencies": { - "lodash": "^4.17.15" - }, - "engines": { - "node": ">= 10" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.5.tgz", + "integrity": "sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==", + "cpu": [ + "arm" + ], "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/chalk/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.5.tgz", + "integrity": "sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==", + "cpu": [ + "arm64" + ], "dev": true, - "engines": { - "node": ">=4" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/chalk/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.5.tgz", + "integrity": "sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/cheerio": { - "version": "0.22.0", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.22.0.tgz", - "integrity": "sha1-qbqoYKP5tZWmuBsahocxIe06Jp4=", + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.5.tgz", + "integrity": "sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==", + "cpu": [ + "loong64" + ], "dev": true, - "dependencies": { - "css-select": "~1.2.0", - "dom-serializer": "~0.1.0", - "entities": "~1.1.1", - "htmlparser2": "^3.9.1", - "lodash.assignin": "^4.0.9", - "lodash.bind": "^4.1.4", - "lodash.defaults": "^4.0.1", - "lodash.filter": "^4.4.0", - "lodash.flatten": "^4.2.0", - "lodash.foreach": "^4.3.0", - "lodash.map": "^4.4.0", - "lodash.merge": "^4.4.0", - "lodash.pick": "^4.2.1", - "lodash.reduce": "^4.4.0", - "lodash.reject": "^4.4.0", - "lodash.some": "^4.4.0" - }, - "engines": { - "node": ">= 0.6" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/cheerio/node_modules/entities": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", - "dev": true + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.5.tgz", + "integrity": "sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.5.tgz", + "integrity": "sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==", + "cpu": [ + "riscv64" + ], "dev": true, - "dependencies": { - "color-name": "1.1.3" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.5.tgz", + "integrity": "sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.5.tgz", + "integrity": "sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==", + "cpu": [ + "s390x" + ], "dev": true, - "engines": { - "node": ">=0.1.90" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/commander": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.6.0.tgz", - "integrity": "sha1-nfflL7Kgyw+4kFjugMMQQiXzfh0=", + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.5.tgz", + "integrity": "sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==", + "cpu": [ + "x64" + ], "dev": true, - "engines": { - "node": ">= 0.6.x" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", - "dev": true + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.5.tgz", + "integrity": "sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.5.tgz", + "integrity": "sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] }, - "node_modules/concurrently": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-3.6.1.tgz", - "integrity": "sha512-/+ugz+gwFSEfTGUxn0KHkY+19XPRTXR8+7oUK/HxgiN1n7FjeJmkrbSiXAJfyQ0zORgJYPaenmymwon51YXH9Q==", + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.5.tgz", + "integrity": "sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "chalk": "^2.4.1", - "commander": "2.6.0", - "date-fns": "^1.23.0", - "lodash": "^4.5.1", - "read-pkg": "^3.0.0", - "rx": "2.3.24", - "spawn-command": "^0.0.2-1", - "supports-color": "^3.2.3", - "tree-kill": "^1.1.0" - }, - "bin": { - "concurrent": "src/main.js", - "concurrently": "src/main.js" - }, - "engines": { - "node": ">=4.0.0" - } + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/corser": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz", - "integrity": "sha1-jtolLsqrWEDc2XXOuQ2TcMgZ/4c=", + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.5.tgz", + "integrity": "sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==", + "cpu": [ + "ia32" + ], "dev": true, - "engines": { - "node": ">= 0.4.0" - } + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.5.tgz", + "integrity": "sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/css-select": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", - "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.5.tgz", + "integrity": "sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", "dev": true, + "license": "MIT", "dependencies": { - "boolbase": "~1.0.0", - "css-what": "2.1", - "domutils": "1.5.1", - "nth-check": "~1.0.1" + "@types/ms": "*" } }, - "node_modules/css-what": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", - "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==", + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true, - "engines": { - "node": "*" - } + "license": "MIT" }, - "node_modules/date-fns": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz", - "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==", - "dev": true + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, + "license": "MIT" }, - "node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "node_modules/@types/katex": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.7.tgz", + "integrity": "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.18.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.12.tgz", + "integrity": "sha512-BICHQ67iqxQGFSzfCFTT7MRQ5XcBjG5aeKh5Ok38UBbPe5fxTyE+aHFxwVrGyr8GNlqFMLKD1D3P2K/1ks8tog==", "dev": true, + "license": "MIT", "dependencies": { - "ms": "^2.1.1" + "undici-types": "~6.21.0" } }, - "node_modules/deep-extend": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.5.1.tgz", - "integrity": "sha512-N8vBdOa+DF7zkRrDCsaOXoCs/E2fJfx9B9MrKnnSiHNh4ws7eSys6YQE4KvT1cecKmOASYQBhbKjeuDD9lT81w==", + "node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } + "license": "MIT" }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" }, - "node_modules/deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "node_modules/@y/protocols": { + "version": "1.0.6-1", + "resolved": "https://registry.npmjs.org/@y/protocols/-/protocols-1.0.6-1.tgz", + "integrity": "sha512-6hyVR4Azg+JVqeyCkPQMsg9BMpB7fgAldsIDwb5EqJTPLXkQuk/mqK/j0rvIZUuPvJjlYSDBIOQWNsy92iXQsQ==", "dev": true, + "license": "MIT", + "dependencies": { + "lib0": "^0.2.85" + }, "engines": { - "node": ">=0.10.0" + "node": ">=16.0.0", + "npm": ">=8.0.0" + }, + "funding": { + "type": "GitHub Sponsors ❤", + "url": "https://github.com/sponsors/dmonad" + }, + "peerDependencies": { + "yjs": "^14.0.0-1 || ^14 || ^13" } }, - "node_modules/define-properties": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", - "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "node_modules/@y/protocols/node_modules/lib0": { + "version": "0.2.114", + "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.114.tgz", + "integrity": "sha512-gcxmNFzA4hv8UYi8j43uPlQ7CGcyMJ2KQb5kZASw6SnAKAf10hK12i2fjrS3Cl/ugZa5Ui6WwIu1/6MIXiHttQ==", "dev": true, + "license": "MIT", "dependencies": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" + "isomorphic.js": "^0.2.4" + }, + "bin": { + "0ecdsa-generate-keypair": "bin/0ecdsa-generate-keypair.js", + "0gentesthtml": "bin/gentesthtml.js", + "0serve": "bin/0serve.js" }, "engines": { - "node": ">= 0.4" + "node": ">=16" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "GitHub Sponsors ❤", + "url": "https://github.com/sponsors/dmonad" } }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, - "dependencies": { - "esutils": "^2.0.2" + "license": "MIT", + "bin": { + "acorn": "bin/acorn" }, "engines": { - "node": ">=6.0.0" + "node": ">=0.4.0" } }, - "node_modules/dom-serializer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", - "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, - "dependencies": { - "domelementtype": "^1.3.0", - "entities": "^1.1.1" + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/dom-serializer/node_modules/entities": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", - "dev": true - }, - "node_modules/domelementtype": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", - "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", - "dev": true - }, - "node_modules/domhandler": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", - "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "license": "MIT", "dependencies": { - "domelementtype": "1" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/domutils": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", - "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, - "dependencies": { - "dom-serializer": "0", - "domelementtype": "1" + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/ecstatic": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/ecstatic/-/ecstatic-3.3.2.tgz", - "integrity": "sha512-fLf9l1hnwrHI2xn9mEDT7KIi22UDqA2jaCwyCbSUJh9a1V+LEUSL/JO/6TIz/QyuBURWUHrFL5Kg2TtO1bkkog==", - "deprecated": "This package is unmaintained and deprecated. See the GH Issue 259.", + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "license": "MIT", "dependencies": { - "he": "^1.1.1", - "mime": "^1.6.0", - "minimist": "^1.1.0", - "url-join": "^2.0.5" + "color-convert": "^2.0.1" }, - "bin": { - "ecstatic": "lib/ecstatic.js" + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" }, - "node_modules/enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-colors": "^4.1.1" + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" }, "engines": { - "node": ">=8.6" - } - }, - "node_modules/entities": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", - "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", - "dev": true, + "node": ">= 0.4" + }, "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", "dev": true, + "license": "MIT", "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-abstract": { - "version": "1.19.5", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.5.tgz", - "integrity": "sha512-Aa2G2+Rd3b6kxEUKTF4TaW67czBLyAv3z7VOhYRU50YBx+bbsYZ9xQP4lMNazePuFlybXI0V4MruPos7qUo5fA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "get-intrinsic": "^1.1.1", - "get-symbol-description": "^1.0.0", - "has": "^1.0.3", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.3", - "is-callable": "^1.2.4", - "is-negative-zero": "^2.0.2", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "is-string": "^1.0.7", - "is-weakref": "^1.0.2", - "object-inspect": "^1.12.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "string.prototype.trimend": "^1.0.4", - "string.prototype.trimstart": "^1.0.4", - "unbox-primitive": "^1.0.1" + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -847,24 +781,19 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/es-shim-unscopables": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", - "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", - "dev": true, - "dependencies": { - "has": "^1.0.3" - } - }, - "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", "dev": true, + "license": "MIT", "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -873,385 +802,238 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/eslint": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.18.0.tgz", - "integrity": "sha512-fbgTiE8BfUJZuBeq2Yi7J3RB3WGUQ9PNuNbmgi6jt9Iv8qrkxfy19Ds3OpL1Pm7zg3BtTVhvcUZbIRQ0wmSjAQ==", + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.0.0", - "@eslint/eslintrc": "^0.3.0", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "enquirer": "^2.3.5", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", - "esquery": "^1.2.0", - "esutils": "^2.0.2", - "file-entry-cache": "^6.0.0", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.0.0", - "globals": "^12.1.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash": "^4.17.20", - "minimatch": "^3.0.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "table": "^6.0.4", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "bin": { - "eslint": "bin/eslint.js" + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">= 0.4" }, "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-config-standard": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-16.0.3.tgz", - "integrity": "sha512-x4fmJL5hGqNJKGHSjnLdgA6U6h1YW/G2dW9fA+cyVur4SK6lyue8+UgNKWlZtUDTXvgKDD/Oa3GQjmB5kjtVvg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "peerDependencies": { - "eslint": "^7.12.1", - "eslint-plugin-import": "^2.22.1", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-promise": "^4.2.1 || ^5.0.0" - } - }, - "node_modules/eslint-config-standard-jsx": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-standard-jsx/-/eslint-config-standard-jsx-10.0.0.tgz", - "integrity": "sha512-hLeA2f5e06W1xyr/93/QJulN/rLbUVUmqTlexv9PRKHFwEC9ffJcH2LvJhMoEqYQBEYafedgGZXH2W8NUpt5lA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "peerDependencies": { - "eslint": "^7.12.1", - "eslint-plugin-react": "^7.21.5" - } - }, - "node_modules/eslint-import-resolver-node": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", - "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", - "dev": true, - "dependencies": { - "debug": "^3.2.7", - "resolve": "^1.20.0" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint-module-utils": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.3.tgz", - "integrity": "sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==", + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", "dev": true, + "license": "MIT", "dependencies": { - "debug": "^3.2.7", - "find-up": "^2.1.0" + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" }, "engines": { - "node": ">=4" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint-plugin-es": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", - "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", "dev": true, + "license": "MIT", "dependencies": { - "eslint-utils": "^2.0.0", - "regexpp": "^3.0.0" + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" }, "engines": { - "node": ">=8.10.0" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=4.19.1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint-plugin-import": { - "version": "2.24.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.24.2.tgz", - "integrity": "sha512-hNVtyhiEtZmpsabL4neEj+6M5DCLgpYyG9nzJY8lZQeQXEn5UPW1DpUdsMHMXsq98dbNm7nt1w9ZMSVpfJdi8Q==", + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", "dev": true, + "license": "MIT", "dependencies": { - "array-includes": "^3.1.3", - "array.prototype.flat": "^1.2.4", - "debug": "^2.6.9", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.6", - "eslint-module-utils": "^2.6.2", - "find-up": "^2.0.0", - "has": "^1.0.3", - "is-core-module": "^2.6.0", - "minimatch": "^3.0.4", - "object.values": "^1.1.4", - "pkg-up": "^2.0.0", - "read-pkg-up": "^3.0.0", - "resolve": "^1.20.0", - "tsconfig-paths": "^3.11.0" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" }, "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0" + "node": ">= 0.4" } }, - "node_modules/eslint-plugin-import/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", "dev": true, + "license": "MIT", "dependencies": { - "ms": "2.0.0" + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint-plugin-import/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" } }, - "node_modules/eslint-plugin-import/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/eslint-plugin-node": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", - "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", "dev": true, + "license": "MIT", "dependencies": { - "eslint-plugin-es": "^3.0.0", - "eslint-utils": "^2.0.0", - "ignore": "^5.1.1", - "minimatch": "^3.0.4", - "resolve": "^1.10.1", - "semver": "^6.1.0" + "possible-typed-array-names": "^1.0.0" }, "engines": { - "node": ">=8.10.0" + "node": ">= 0.4" }, - "peerDependencies": { - "eslint": ">=5.16.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint-plugin-node/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, - "bin": { - "semver": "bin/semver.js" - } + "license": "MIT" }, - "node_modules/eslint-plugin-promise": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-5.1.1.tgz", - "integrity": "sha512-XgdcdyNzHfmlQyweOPTxmc7pIsS6dE4MvwhXWMQ2Dxs1XAL2GJDilUsjWen6TWik0aSI+zD/PqocZBblcm9rdA==", + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "peerDependencies": { - "eslint": "^7.0.0" + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/eslint-plugin-react": { - "version": "7.25.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.25.3.tgz", - "integrity": "sha512-ZMbFvZ1WAYSZKY662MBVEWR45VaBT6KSJCiupjrNlcdakB90juaZeDCbJq19e73JZQubqFtgETohwgAt8u5P6w==", + "node_modules/builtins": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.1.0.tgz", + "integrity": "sha512-SW9lzGTLvWTP1AY8xeAMZimqDrIaSdLQUcVr9DMef51niJ022Ri87SwRRKYm4A6iHfkPaiVUu/Duw2Wc4J7kKg==", "dev": true, + "license": "MIT", "dependencies": { - "array-includes": "^3.1.3", - "array.prototype.flatmap": "^1.2.4", - "doctrine": "^2.1.0", - "estraverse": "^5.2.0", - "jsx-ast-utils": "^2.4.1 || ^3.0.0", - "minimatch": "^3.0.4", - "object.entries": "^1.1.4", - "object.fromentries": "^2.0.4", - "object.hasown": "^1.0.0", - "object.values": "^1.1.4", - "prop-types": "^15.7.2", - "resolve": "^2.0.0-next.3", - "string.prototype.matchall": "^4.0.5" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7" + "semver": "^7.0.0" } }, - "node_modules/eslint-plugin-react/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "node_modules/builtins/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "dev": true, - "dependencies": { - "esutils": "^2.0.2" + "license": "ISC", + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": ">=0.10.0" + "node": ">=10" } }, - "node_modules/eslint-plugin-react/node_modules/resolve": { - "version": "2.0.0-next.3", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.3.tgz", - "integrity": "sha512-W8LucSynKUIDu9ylraa7ueVZ7hc0uAgJBxVsQSKOXOyle8a93qXhcz+XAXZ8bIq2d6i4Ehddn6Evt+0/UwKk6Q==", + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", "dev": true, + "license": "MIT", "dependencies": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "dev": true, + "license": "MIT", "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" }, "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/eslint-scope/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" + "node": ">= 0.4" } }, - "node_modules/eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "dev": true, + "license": "MIT", "dependencies": { - "eslint-visitor-keys": "^1.1.0" + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { - "node": ">=6" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "engines": { - "node": ">=10" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, + "license": "MIT", "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=6" } }, - "node_modules/eslint/node_modules/chalk": { + "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -1263,335 +1045,320 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/eslint/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=7.0.0" + "node": ">=8" } }, - "node_modules/eslint/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/eslint/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/eslint/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", "dev": true, - "engines": { - "node": ">=8" + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/eslint/node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", "dev": true, - "engines": { - "node": ">= 4" + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/eslint/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/eslint/node_modules/semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, + "license": "ISC", "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" }, "engines": { - "node": ">=10" + "node": ">=12" } }, - "node_modules/eslint/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, - "dependencies": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } + "license": "MIT" }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, "engines": { - "node": ">=4" + "node": ">=8" } }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">=4" + "node": ">=8" } }, - "node_modules/esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, + "license": "MIT", "dependencies": { - "estraverse": "^5.1.0" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=0.10" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, + "license": "MIT", "dependencies": { - "estraverse": "^5.2.0" + "color-name": "~1.1.4" }, "engines": { - "node": ">=4.0" + "node": ">=7.0.0" } }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true + "license": "MIT" }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "node_modules/commander": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", "dev": true, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=18" } }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "node_modules/concurrently": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz", + "integrity": "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==", "dev": true, + "license": "MIT", "dependencies": { - "flat-cache": "^3.0.4" + "chalk": "4.1.2", + "rxjs": "7.8.2", + "shell-quote": "1.8.3", + "supports-color": "8.1.1", + "tree-kill": "1.2.2", + "yargs": "17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" } }, - "node_modules/find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, + "license": "MIT", "dependencies": { - "locate-path": "^2.0.0" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" }, "engines": { - "node": ">=4" + "node": ">= 8" } }, - "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", "dev": true, + "license": "MIT", "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/flatted": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", - "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", - "dev": true - }, - "node_modules/follow-redirects": { - "version": "1.14.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", - "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==", + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, "engines": { - "node": ">=4.0" + "node": ">= 0.4" }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/inspect-js" } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "node_modules/functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "node_modules/decode-named-character-reference": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz", + "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==", "dev": true, + "license": "MIT", "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" + "character-entities": "^2.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/get-stdin": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz", - "integrity": "sha1-Ei4WFZHiH/TFJTAwVpPyDmOTo5g=", + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "dev": true, + "license": "MIT", "engines": { - "node": ">=0.12.0" + "node": ">=4.0.0" } }, - "node_modules/get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true, + "license": "MIT" + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -1600,126 +1367,174 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "dev": true, + "license": "MIT", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" }, "engines": { - "node": "*" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, + "license": "MIT", "engines": { - "node": ">= 6" + "node": ">=6" } }, - "node_modules/globals": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", - "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", "dev": true, + "license": "MIT", "dependencies": { - "type-fest": "^0.8.1" - }, - "engines": { - "node": ">=8" + "dequal": "^2.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } }, - "node_modules/graceful-readlink": { + "node_modules/dunder-proto": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", - "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", - "dev": true - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "dev": true, + "license": "MIT", "dependencies": { - "function-bind": "^1.1.1" + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" }, "engines": { - "node": ">= 0.4.0" + "node": ">= 0.4" } }, - "node_modules/has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "license": "MIT" }, - "node_modules/has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true, - "engines": { - "node": ">=0.10.0" - } + "license": "MIT" }, - "node_modules/has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.1" + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" } }, - "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "node_modules/es-abstract": { + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", + "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", "dev": true, + "license": "MIT", "dependencies": { - "has-symbols": "^1.0.2" + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" }, "engines": { "node": ">= 0.4" @@ -1728,176 +1543,106 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "bin": { - "he": "bin/he" - } - }, - "node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, - "node_modules/htmlparser2": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", - "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "dev": true, - "dependencies": { - "domelementtype": "^1.3.1", - "domhandler": "^2.3.0", - "domutils": "^1.5.1", - "entities": "^1.1.1", - "inherits": "^2.0.1", - "readable-stream": "^3.1.1" + "license": "MIT", + "engines": { + "node": ">= 0.4" } }, - "node_modules/htmlparser2/node_modules/entities": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", - "dev": true - }, - "node_modules/http-proxy": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", - "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "dev": true, - "dependencies": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" - }, + "license": "MIT", "engines": { - "node": ">=8.0.0" + "node": ">= 0.4" } }, - "node_modules/http-server": { - "version": "0.12.3", - "resolved": "https://registry.npmjs.org/http-server/-/http-server-0.12.3.tgz", - "integrity": "sha512-be0dKG6pni92bRjq0kvExtj/NrrAd28/8fCXkaI/4piTwQMSDSLMhWyW0NI1V+DBI3aa1HMlQu46/HjVLfmugA==", + "node_modules/es-iterator-helpers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz", + "integrity": "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==", "dev": true, + "license": "MIT", "dependencies": { - "basic-auth": "^1.0.3", - "colors": "^1.4.0", - "corser": "^2.0.1", - "ecstatic": "^3.3.2", - "http-proxy": "^1.18.0", - "minimist": "^1.2.5", - "opener": "^1.5.1", - "portfinder": "^1.0.25", - "secure-compare": "3.0.1", - "union": "~0.5.0" - }, - "bin": { - "hs": "bin/http-server", - "http-server": "bin/http-server" + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.6", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.4", + "safe-array-concat": "^1.1.3" }, "engines": { - "node": ">=6" - } - }, - "node_modules/ignore": { - "version": "5.1.9", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.9.tgz", - "integrity": "sha512-2zeMQpbKz5dhZ9IwL0gbxSW5w0NK/MSAMtNuhgIHEPmaU3vPdKPL0UdvUCXs5SS4JAwsBxysK5sFMW8ocFiVjQ==", - "dev": true, - "engines": { - "node": ">= 4" + "node": ">= 0.4" } }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "dev": true, + "license": "MIT", "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" + "es-errors": "^1.3.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true, "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" + "node": ">= 0.4" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true - }, - "node_modules/internal-slot": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", - "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "dev": true, + "license": "MIT", "dependencies": { - "get-intrinsic": "^1.1.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" } }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "node_modules/is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", "dev": true, + "license": "MIT", "dependencies": { - "has-bigints": "^1.0.1" + "hasown": "^2.0.2" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">= 0.4" } }, - "node_modules/is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" }, "engines": { "node": ">= 0.4" @@ -1906,809 +1651,792 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-callable": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", - "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, + "license": "MIT", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=6" } }, - "node_modules/is-core-module": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", - "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, - "dependencies": { - "has": "^1.0.3" + "license": "MIT", + "engines": { + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, + "license": "MIT", "dependencies": { - "has-tostringtag": "^1.0.0" + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" }, "engines": { - "node": ">= 0.4" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://opencollective.com/eslint" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "node_modules/eslint-config-standard": { + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.1.0.tgz", + "integrity": "sha512-IwHwmaBNtDK4zDHQukFDW5u/aTb8+meQWZvNFWkiGmbWjD6bqyuSSBxxXKkCftCUzc1zwCH2m/baCNDLGmuO5Q==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=12.0.0" + }, + "peerDependencies": { + "eslint": "^8.0.1", + "eslint-plugin-import": "^2.25.2", + "eslint-plugin-n": "^15.0.0 || ^16.0.0 ", + "eslint-plugin-promise": "^6.0.0" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "node_modules/eslint-config-standard-jsx": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard-jsx/-/eslint-config-standard-jsx-11.0.0.tgz", + "integrity": "sha512-+1EV/R0JxEK1L0NGolAr8Iktm3Rgotx3BKwgaX+eAuSX8D952LULKtjgZD3F+e6SvibONnhLwoTi9DPxN5LvvQ==", "dev": true, - "engines": { - "node": ">=8" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "peerDependencies": { + "eslint": "^8.8.0", + "eslint-plugin-react": "^7.28.0" } }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", "dev": true, + "license": "MIT", "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" } }, - "node_modules/is-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", - "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=", - "dev": true - }, - "node_modules/is-negative-zero": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" } }, - "node_modules/is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "node_modules/eslint-module-utils": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", + "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", "dev": true, + "license": "MIT", "dependencies": { - "has-tostringtag": "^1.0.0" + "debug": "^3.2.7" }, "engines": { - "node": ">= 0.4" + "node": ">=4" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependenciesMeta": { + "eslint": { + "optional": true + } } }, - "node_modules/is-reference": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", - "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, + "license": "MIT", "dependencies": { - "@types/estree": "*" + "ms": "^2.1.1" } }, - "node_modules/is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "node_modules/eslint-plugin-es": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-4.1.0.tgz", + "integrity": "sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">=8.10.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=4.19.1" } }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "node_modules/eslint-plugin-es/node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2" + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/mysticatea" } }, - "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "node_modules/eslint-plugin-es/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, + "license": "Apache-2.0", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=4" } }, - "node_modules/is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "node_modules/eslint-plugin-import": { + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, + "license": "MIT", "dependencies": { - "has-symbols": "^1.0.2" + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.9", + "array.prototype.findlastindex": "^1.2.6", + "array.prototype.flat": "^1.3.3", + "array.prototype.flatmap": "^1.3.3", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.1", + "hasown": "^2.0.2", + "is-core-module": "^2.16.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.1", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.9", + "tsconfig-paths": "^3.15.0" }, "engines": { - "node": ">= 0.4" + "node": ">=4" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" } }, - "node_modules/is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "ms": "^2.1.1" } }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "node_modules/isomorphic.js": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.2.5.tgz", - "integrity": "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==", - "funding": { - "type": "GitHub Sponsors ❤", - "url": "https://github.com/sponsors/dmonad" + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" } }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "node_modules/js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "brace-expansion": "^1.1.7" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "engines": { + "node": "*" } }, - "node_modules/js-yaml/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "node_modules/eslint-plugin-n": { + "version": "15.7.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-15.7.0.tgz", + "integrity": "sha512-jDex9s7D/Qial8AGVIHq4W7NswpUD5DPDL2RH8Lzd9EloWUuvUkHfv4FRLMipH5q2UtyurorBkPeNi1wVWNh3Q==", "dev": true, + "license": "MIT", "dependencies": { - "sprintf-js": "~1.0.2" + "builtins": "^5.0.1", + "eslint-plugin-es": "^4.1.0", + "eslint-utils": "^3.0.0", + "ignore": "^5.1.1", + "is-core-module": "^2.11.0", + "minimatch": "^3.1.2", + "resolve": "^1.22.1", + "semver": "^7.3.8" + }, + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=7.0.0" } }, - "node_modules/js2xmlparser": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", - "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", + "node_modules/eslint-plugin-n/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, - "dependencies": { - "xmlcreate": "^2.0.4" + "license": "MIT", + "engines": { + "node": ">= 4" } }, - "node_modules/jsdoc": { - "version": "3.6.10", - "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.10.tgz", - "integrity": "sha512-IdQ8ppSo5LKZ9o3M+LKIIK8i00DIe5msDvG3G81Km+1dhy0XrOWD0Ji8H61ElgyEj/O9KRLokgKbAM9XX9CJAg==", + "node_modules/eslint-plugin-n/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { - "@babel/parser": "^7.9.4", - "@types/markdown-it": "^12.2.3", - "bluebird": "^3.7.2", - "catharsis": "^0.9.0", - "escape-string-regexp": "^2.0.0", - "js2xmlparser": "^4.0.2", - "klaw": "^4.0.1", - "markdown-it": "^12.3.2", - "markdown-it-anchor": "^8.4.1", - "marked": "^4.0.10", - "mkdirp": "^1.0.4", - "requizzle": "^0.2.3", - "strip-json-comments": "^3.1.0", - "taffydb": "2.6.2", - "underscore": "~1.13.2" - }, - "bin": { - "jsdoc": "jsdoc.js" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=8.15.0" + "node": "*" } }, - "node_modules/jsdoc/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "node_modules/eslint-plugin-n/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, "engines": { - "node": ">=8" + "node": ">=10" } }, - "node_modules/json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "node_modules/eslint-plugin-promise": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.6.0.tgz", + "integrity": "sha512-57Zzfw8G6+Gq7axm2Pdo3gW/Rx3h9Yywgn61uE/3elTCOePEHVrn2i5CdfBwA1BLK0Q0WqctICIUSqXZW/VprQ==", "dev": true, - "dependencies": { - "minimist": "^1.2.0" + "license": "ISC", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, - "bin": { - "json5": "lib/cli.js" + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" } }, - "node_modules/jsonc-parser": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.2.1.tgz", - "integrity": "sha512-o6/yDBYccGvTz1+QFevz6l6OBZ2+fMVu2JZ9CIhzsYRX4mjaK5IyX9eldUdCmga16zlgQxyrj5pt9kzuj2C02w==", - "dev": true - }, - "node_modules/jsx-ast-utils": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.2.tgz", - "integrity": "sha512-HDAyJ4MNQBboGpUnHAVUNJs6X0lh058s6FuixsFGP7MgJYpD6Vasd6nzSG5iIfXu1zAYlHJ/zsOKNlrenTUBnw==", + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", "dev": true, + "license": "MIT", "dependencies": { - "array-includes": "^3.1.4", - "object.assign": "^4.1.2" + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" }, "engines": { - "node": ">=4.0" + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" } }, - "node_modules/klaw": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-4.0.1.tgz", - "integrity": "sha512-pgsE40/SvC7st04AHiISNewaIMUbY5V/K8b21ekiPiFoYs/EYSdsGa+FJArB1d441uq4Q8zZyIxvAzkGNlBdRw==", + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, "engines": { - "node": ">=14.14.0" + "node": ">=0.10.0" } }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "node_modules/eslint-plugin-react/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">= 0.8.0" + "node": "*" } }, - "node_modules/lib0": { - "version": "0.2.51", - "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.51.tgz", - "integrity": "sha512-05Erb3465CxJa38LQlMz4EbetNvRna1S3BzqEjC0/pmp5cQuQSfNNmeS0722Wev1dRlMUp2Cql0gQ55krSXf2Q==", + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, + "license": "MIT", "dependencies": { - "isomorphic.js": "^0.2.4" + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" }, - "engines": { - "node": ">=12" + "bin": { + "resolve": "bin/resolve" }, "funding": { - "type": "GitHub Sponsors ❤", - "url": "https://github.com/sponsors/dmonad" - } - }, - "node_modules/linkify-it": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", - "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", - "dev": true, - "dependencies": { - "uc.micro": "^1.0.1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" }, "engines": { - "node": ">=4" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", "dev": true, + "license": "MIT", "dependencies": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" + "eslint-visitor-keys": "^2.0.0" }, "engines": { - "node": ">=4" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "node_modules/lodash.assignin": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz", - "integrity": "sha1-uo31+4QesKPoBEIysOJjqNxqKKI=", - "dev": true - }, - "node_modules/lodash.bind": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-4.2.1.tgz", - "integrity": "sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU=", - "dev": true - }, - "node_modules/lodash.defaults": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=", - "dev": true - }, - "node_modules/lodash.differencewith": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.differencewith/-/lodash.differencewith-4.5.0.tgz", - "integrity": "sha1-uvr7yRi1UVTheRdqALsK76rIVLc=", - "dev": true - }, - "node_modules/lodash.filter": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz", - "integrity": "sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4=", - "dev": true - }, - "node_modules/lodash.flatten": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=", - "dev": true - }, - "node_modules/lodash.foreach": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz", - "integrity": "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM=", - "dev": true - }, - "node_modules/lodash.map": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", - "integrity": "sha1-dx7Hg540c9nEzeKLGTlMNWL09tM=", - "dev": true - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/lodash.pick": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", - "integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=", - "dev": true - }, - "node_modules/lodash.reduce": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", - "integrity": "sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs=", - "dev": true - }, - "node_modules/lodash.reject": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.reject/-/lodash.reject-4.6.0.tgz", - "integrity": "sha1-gNZJLcFHCGS79YNTO2UfQqn1JBU=", - "dev": true - }, - "node_modules/lodash.some": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", - "integrity": "sha1-G7nzFO9ri63tE7VJFpsqlF62jk0=", - "dev": true - }, - "node_modules/lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", - "dev": true - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" }, - "bin": { - "loose-envify": "cli.js" + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" } }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, + "license": "Apache-2.0", "engines": { "node": ">=10" } }, - "node_modules/magic-string": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", - "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, - "dependencies": { - "sourcemap-codec": "^1.4.8" + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/markdown-it": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", - "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "node_modules/eslint/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, - "dependencies": { - "argparse": "^2.0.1", - "entities": "~2.1.0", - "linkify-it": "^3.0.1", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" - }, - "bin": { - "markdown-it": "bin/markdown-it.js" + "license": "MIT", + "engines": { + "node": ">=8" } }, - "node_modules/markdown-it-anchor": { - "version": "8.6.2", - "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.2.tgz", - "integrity": "sha512-JNaekTlIwwyYGBN3zifZDxgz4bSL8sbEj58fdTZGmPSMMGXBZapFjcZk2I33Jy79c1fvCKHpF7MA/67FOTjvzA==", + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, - "peerDependencies": { - "@types/markdown-it": "*", - "markdown-it": "*" + "license": "MIT", + "engines": { + "node": ">= 4" } }, - "node_modules/markdownlint": { - "version": "0.20.4", - "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.20.4.tgz", - "integrity": "sha512-jpfaPgjT0OpeBbemjYNZbzGG3hCLcAIvrm/pEY3+q/szDScG6ZonDacqySVRJAv9glbo8y4wBPJ0wgW17+9GGA==", + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { - "markdown-it": "10.0.0" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=10" + "node": "*" } }, - "node_modules/markdownlint-cli": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/markdownlint-cli/-/markdownlint-cli-0.23.2.tgz", - "integrity": "sha512-OSl5OZ8xzGN6z355cqRkiq67zPi3reJimklaF72p0554q85Dng5ToOjjSB9tDKZebSt85jX8cp+ruoQlPqOsPA==", - "dev": true, - "dependencies": { - "commander": "~2.9.0", - "deep-extend": "~0.5.1", - "get-stdin": "~5.0.1", - "glob": "~7.1.2", - "ignore": "~5.1.4", - "js-yaml": "~3.13.1", - "jsonc-parser": "~2.2.0", - "lodash.differencewith": "~4.5.0", - "lodash.flatten": "~4.4.0", - "markdownlint": "~0.20.4", - "markdownlint-rule-helpers": "~0.11.0", - "minimatch": "~3.0.4", - "minimist": "~1.2.5", - "rc": "~1.2.7" - }, - "bin": { - "markdownlint": "markdownlint.js" + "node_modules/eslint/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">=10" + "node": ">=8" } }, - "node_modules/markdownlint-cli/node_modules/commander": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", - "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "graceful-readlink": ">= 1.0.0" + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" }, "engines": { - "node": ">= 0.6.x" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/markdownlint-cli/node_modules/glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "estraverse": "^5.1.0" }, "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=0.10" } }, - "node_modules/markdownlint-cli/node_modules/minimatch": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", - "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "brace-expansion": "^1.1.7" + "estraverse": "^5.2.0" }, "engines": { - "node": "*" + "node": ">=4.0" } }, - "node_modules/markdownlint-rule-helpers": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/markdownlint-rule-helpers/-/markdownlint-rule-helpers-0.11.0.tgz", - "integrity": "sha512-PhGii9dOiDJDXxiRMpK8N0FM9powprvRPsXALgkjlSPTwLh6ymH+iF3iUe3nq8KGu26tclFBlLL5xAGy/zb7FA==", - "dev": true - }, - "node_modules/markdownlint/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" } }, - "node_modules/markdownlint/node_modules/entities": { + "node_modules/esutils": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz", - "integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==", - "dev": true - }, - "node_modules/markdownlint/node_modules/linkify-it": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz", - "integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, - "dependencies": { - "uc.micro": "^1.0.1" + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" } }, - "node_modules/markdownlint/node_modules/markdown-it": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-10.0.0.tgz", - "integrity": "sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg==", + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", "dev": true, + "license": "ISC", "dependencies": { - "argparse": "^1.0.7", - "entities": "~2.0.0", - "linkify-it": "^2.0.0", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" - }, - "bin": { - "markdown-it": "bin/markdown-it.js" + "reusify": "^1.0.4" } }, - "node_modules/marked": { - "version": "4.0.14", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.14.tgz", - "integrity": "sha512-HL5sSPE/LP6U9qKgngIIPTthuxC0jrfxpYMZ3LdGDD3vTnLs59m2Z7r6+LNDR3ToqEQdkKd6YaaEfJhodJmijQ==", + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, - "bin": { - "marked": "bin/marked.js" + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" }, "engines": { - "node": ">= 12" + "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=", - "dev": true - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, - "bin": { - "mime": "cli.js" + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dev": true, + "license": "MIT", "dependencies": { - "brace-expansion": "^1.1.7" + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" }, "engines": { - "node": "*" + "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", - "dev": true + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", "dev": true, - "bin": { - "mkdirp": "bin/cmd.js" + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" }, "engines": { - "node": ">=10" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "dev": true, + "license": "ISC", "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/nth-check": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", - "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true, - "dependencies": { - "boolbase": "~1.0.0" - } + "license": "ISC" }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=0.10.0" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/object-inspect": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", - "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==", + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "dev": true, + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" }, "engines": { "node": ">= 0.4" @@ -2717,29 +2445,53 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/object.entries": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.5.tgz", - "integrity": "sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g==", + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - }, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" } }, - "node_modules/object.fromentries": { + "node_modules/get-caller-file": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.5.tgz", - "integrity": "sha512-CAyG5mWQRRiBU57Re4FKoTBjXfDoNwdFVH2Y1tS9PqCsfUTymAohOkEMSG3aRNKmv4lV3O7p1et7c187q6bynw==", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -2748,28 +2500,43 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/object.hasown": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.0.tgz", - "integrity": "sha512-MhjYRfj3GBlhSkDHo6QmvgjRLXQ2zndabdf3nX0yTyZK9rPfxb6uRpAac8HXNLy1GpqWtZ81Qh4v3uOls2sRAg==", + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "dev": true, + "license": "MIT", "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stdin": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz", + "integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/object.values": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", - "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" }, "engines": { "node": ">= 0.4" @@ -2778,226 +2545,216 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "node_modules/glob": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz", + "integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==", "dev": true, + "license": "ISC", "dependencies": { - "wrappy": "1" - } - }, - "node_modules/opener": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", - "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", - "dev": true, + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.0.3", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, "bin": { - "opener": "bin/opener-bin.js" + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, + "license": "ISC", "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "is-glob": "^4.0.3" }, "engines": { - "node": ">= 0.8.0" + "node": ">=10.13.0" } }, - "node_modules/p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, + "license": "MIT", "dependencies": { - "p-try": "^1.0.0" + "type-fest": "^0.20.2" }, "engines": { - "node": ">=4" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", "dev": true, + "license": "MIT", "dependencies": { - "p-limit": "^1.1.0" + "define-properties": "^1.2.1", + "gopd": "^1.0.1" }, "engines": { - "node": ">=4" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "dev": true, + "license": "MIT", "engines": { - "node": ">=4" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" + "node": ">= 0.4" }, - "engines": { - "node": ">=6" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true, - "dependencies": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - }, - "engines": { - "node": ">=4" - } + "license": "ISC" }, - "node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true, - "engines": { - "node": ">=4" - } + "license": "MIT" }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", "dev": true, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "node_modules/path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dev": true, + "license": "MIT", "dependencies": { - "pify": "^3.0.0" + "es-define-property": "^1.0.0" }, - "engines": { - "node": ">=4" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, "engines": { - "node": ">=8.6" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true, + "license": "MIT", "engines": { - "node": ">=4" - } - }, - "node_modules/pkg-conf": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-3.1.0.tgz", - "integrity": "sha512-m0OTbR/5VPNPqO1ph6Fqbj7Hv6QU7gR/tQW40ZqrL1rjgCU85W6C1bJn0BItuJqnR98PWzw7Z8hHeChD1WrgdQ==", - "dev": true, - "dependencies": { - "find-up": "^3.0.0", - "load-json-file": "^5.2.0" + "node": ">= 0.4" }, - "engines": { - "node": ">=6" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/pkg-conf/node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, + "license": "MIT", "dependencies": { - "locate-path": "^3.0.0" + "has-symbols": "^1.0.3" }, "engines": { - "node": ">=6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/pkg-conf/node_modules/load-json-file": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-5.3.0.tgz", - "integrity": "sha512-cJGP40Jc/VXUsp8/OrnyKyTZ1y6v/dphm3bioS+RrKXjK2BB6wHUd6JptZEFDGgGahMT+InnZO5i1Ei9mpC8Bw==", + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dev": true, + "license": "MIT", "dependencies": { - "graceful-fs": "^4.1.15", - "parse-json": "^4.0.0", - "pify": "^4.0.1", - "strip-bom": "^3.0.0", - "type-fest": "^0.3.0" + "function-bind": "^1.1.2" }, "engines": { - "node": ">=6" + "node": ">= 0.4" } }, - "node_modules/pkg-conf/node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", "dev": true, - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, + "license": "MIT", "engines": { - "node": ">=6" + "node": ">= 4" } }, - "node_modules/pkg-conf/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, + "license": "MIT", "dependencies": { - "p-try": "^2.0.0" + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" }, "engines": { "node": ">=6" @@ -3006,225 +2763,220 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/pkg-conf/node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, - "dependencies": { - "p-limit": "^2.0.0" - }, + "license": "MIT", "engines": { - "node": ">=6" + "node": ">=0.8.19" } }, - "node_modules/pkg-conf/node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, - "engines": { - "node": ">=6" + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" } }, - "node_modules/pkg-conf/node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true, - "engines": { - "node": ">=6" - } + "license": "ISC" }, - "node_modules/pkg-conf/node_modules/type-fest": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz", - "integrity": "sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==", + "node_modules/ini": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", + "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", "dev": true, + "license": "ISC", "engines": { - "node": ">=6" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz", - "integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=", + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", "dev": true, + "license": "MIT", "dependencies": { - "find-up": "^2.1.0" + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" }, "engines": { - "node": ">=4" + "node": ">= 0.4" } }, - "node_modules/portfinder": { - "version": "1.0.28", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", - "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", "dev": true, - "dependencies": { - "async": "^2.6.2", - "debug": "^3.1.1", - "mkdirp": "^0.5.5" - }, - "engines": { - "node": ">= 0.12.0" + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/portfinder/node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", "dev": true, + "license": "MIT", "dependencies": { - "minimist": "^1.2.6" + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, "engines": { - "node": ">=0.4.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true, - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } + "license": "MIT" }, - "node_modules/punycode": { + "node_modules/is-async-function": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, "engines": { - "node": ">=6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/qs": { - "version": "6.10.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", - "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", "dev": true, + "license": "MIT", "dependencies": { - "side-channel": "^1.0.4" + "has-bigints": "^1.0.2" }, "engines": { - "node": ">=0.6" + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", "dev": true, + "license": "MIT", "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/rc/node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true, "engines": { - "node": ">=4.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/rc/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "dev": true, + "license": "MIT", "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true - }, - "node_modules/read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", - "dev": true, - "dependencies": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" + "node": ">= 0.4" }, - "engines": { - "node": ">=4" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/read-pkg-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", - "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "dev": true, + "license": "MIT", "dependencies": { - "find-up": "^2.0.0", - "read-pkg": "^3.0.0" + "hasown": "^2.0.2" }, "engines": { - "node": ">=4" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", "dev": true, + "license": "MIT", "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" }, "engines": { - "node": ">= 6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/regexp.prototype.flags": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", - "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "functions-have-names": "^1.2.2" + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -3233,3755 +2985,3175 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", "dev": true, - "engines": { - "node": ">=8" - }, + "license": "MIT", "funding": { - "url": "https://github.com/sponsors/mysticatea" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", - "dev": true - }, - "node_modules/requizzle": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.3.tgz", - "integrity": "sha512-YanoyJjykPxGHii0fZP0uUPEXpvqfBDxWV7s6GKAiiOsiqhX6vHNyW3Qzdmqp/iq/ExbhaGbVrjB4ruEVSM4GQ==", - "dev": true, - "dependencies": { - "lodash": "^4.17.14" - } - }, - "node_modules/resolve": { - "version": "1.22.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", - "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", "dev": true, + "license": "MIT", "dependencies": { - "is-core-module": "^2.8.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" + "call-bound": "^1.0.3" }, - "bin": { - "resolve": "bin/resolve" + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, + "license": "MIT", "engines": { - "node": ">=4" + "node": ">=8" } }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", "dev": true, + "license": "MIT", "dependencies": { - "glob": "^7.1.3" + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" }, - "bin": { - "rimraf": "bin.js" + "engines": { + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/rollup": { - "version": "2.70.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.70.2.tgz", - "integrity": "sha512-EitogNZnfku65I1DD5Mxe8JYRUCy0hkK5X84IlDtUs+O6JRMpRciXTzyCUuX11b5L5pvjH+OmFXiQ3XjabcXgg==", + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, - "bin": { - "rollup": "dist/bin/rollup" + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" }, "engines": { - "node": ">=10.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" + "node": ">=0.10.0" } }, - "node_modules/rx": { - "version": "2.3.24", - "resolved": "https://registry.npmjs.org/rx/-/rx-2.3.24.tgz", - "integrity": "sha1-FPlQpCF9fjXapxu8vljv9o6ksrc=", - "dev": true - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/secure-compare": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz", - "integrity": "sha1-8aAymzCLIh+uN7mXTz1XjQypmeM=", - "dev": true - }, - "node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", "dev": true, - "bin": { - "semver": "bin/semver" + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/slice-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, + "license": "MIT", "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/slice-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", "dev": true, + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/slice-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "dev": true - }, - "node_modules/spawn-command": { - "version": "0.0.2-1", - "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2-1.tgz", - "integrity": "sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A=", - "dev": true - }, - "node_modules/spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "dev": true, - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", "dev": true, - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/spdx-license-ids": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz", - "integrity": "sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g==", - "dev": true - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "node_modules/standard": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/standard/-/standard-16.0.4.tgz", - "integrity": "sha512-2AGI874RNClW4xUdM+bg1LRXVlYLzTNEkHmTG5mhyn45OhbgwA+6znowkOGYy+WMb5HRyELvtNy39kcdMQMcYQ==", + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], + "license": "MIT", "dependencies": { - "eslint": "~7.18.0", - "eslint-config-standard": "16.0.3", - "eslint-config-standard-jsx": "10.0.0", - "eslint-plugin-import": "~2.24.2", - "eslint-plugin-node": "~11.1.0", - "eslint-plugin-promise": "~5.1.0", - "eslint-plugin-react": "~7.25.1", - "standard-engine": "^14.0.1" - }, - "bin": { - "standard": "bin/cmd.js" + "call-bound": "^1.0.3" }, "engines": { - "node": ">=10.12.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/standard-engine": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/standard-engine/-/standard-engine-14.0.1.tgz", - "integrity": "sha512-7FEzDwmHDOGva7r9ifOzD3BGdTbA7ujJ50afLVdW/tK14zQEptJjbFuUfn50irqdHDcTbNh0DTIoMPynMCXb0Q==", + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], + "license": "MIT", "dependencies": { - "get-stdin": "^8.0.0", - "minimist": "^1.2.5", - "pkg-conf": "^3.1.0", - "xdg-basedir": "^4.0.0" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { - "node": ">=8.10" - } - }, - "node_modules/standard-engine/node_modules/get-stdin": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz", - "integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==", - "dev": true, - "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.2.0" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", "dev": true, + "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/string.prototype.matchall": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz", - "integrity": "sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg==", + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1", - "get-intrinsic": "^1.1.1", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.3", - "regexp.prototype.flags": "^1.4.1", - "side-channel": "^1.0.4" + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/string.prototype.trimend": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", - "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "license": "MIT", + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", - "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", "dev": true, - "engines": { - "node": ">=4" - } + "license": "MIT" }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true, - "engines": { - "node": ">=8" - }, + "license": "ISC" + }, + "node_modules/isomorphic.js": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.2.5.tgz", + "integrity": "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==", + "license": "MIT", "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "GitHub Sponsors ❤", + "url": "https://github.com/sponsors/dmonad" } }, - "node_modules/supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^1.0.0" + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" }, "engines": { - "node": ">=0.8.0" + "node": ">= 0.4" } }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "node_modules/jackspeak": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", + "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, "engines": { - "node": ">= 0.4" + "node": "20 || >=22" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/table": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/table/-/table-6.8.0.tgz", - "integrity": "sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==", + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true, - "dependencies": { - "ajv": "^8.0.1", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=10.0.0" - } + "license": "MIT" }, - "node_modules/table/node_modules/ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, + "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" + "argparse": "^2.0.1" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/table/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "node_modules/taffydb": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", - "integrity": "sha1-fLy2S1oUG2ou/CxdLGe04VCyomg=", - "dev": true - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" }, - "node_modules/tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", "dev": true, - "bin": { - "tree-kill": "cli.js" - } + "license": "MIT" }, - "node_modules/tsconfig-paths": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", - "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true, - "dependencies": { - "@types/json5": "^0.0.29", - "json5": "^1.0.1", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - } + "license": "MIT" }, - "node_modules/tui-jsdoc-template": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tui-jsdoc-template/-/tui-jsdoc-template-1.2.2.tgz", - "integrity": "sha512-oqw0IYaot86VJ2owKBozJnilgta0Z55x8r9PeHj7vb+jDoSvJGRUQUcgs56SZh9HE20fx54Pe75p84X85/ygLA==", + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true, - "dependencies": { - "cheerio": "^0.22.0" - } + "license": "MIT" }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, + "license": "MIT", "dependencies": { - "prelude-ls": "^1.2.1" + "minimist": "^1.2.0" }, - "engines": { - "node": ">= 0.8.0" + "bin": { + "json5": "lib/cli.js" } }, - "node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonpointer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", + "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/typescript": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz", - "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==", + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" }, "engines": { - "node": ">=4.2.0" + "node": ">=4.0" } }, - "node_modules/uc.micro": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", - "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", - "dev": true - }, - "node_modules/unbox-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", - "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "node_modules/katex": { + "version": "0.16.25", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.25.tgz", + "integrity": "sha512-woHRUZ/iF23GBP1dkDQMh1QBad9dmr8/PAwNA54VrSOVYgI12MAcE14TqnDdQOdzyEonGzMepYnqBMYdsoAr8Q==", "dev": true, + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", "dependencies": { - "function-bind": "^1.1.1", - "has-bigints": "^1.0.1", - "has-symbols": "^1.0.2", - "which-boxed-primitive": "^1.0.2" + "commander": "^8.3.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "bin": { + "katex": "cli.js" } }, - "node_modules/underscore": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.2.tgz", - "integrity": "sha512-ekY1NhRzq0B08g4bGuX4wd2jZx5GnKz6mKSqFL4nqBlfyMGiG10gDFhDTMEfYmDL6Jy0FUIZp7wiRB+0BP7J2g==", - "dev": true - }, - "node_modules/union": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz", - "integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==", + "node_modules/katex/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", "dev": true, - "dependencies": { - "qs": "^6.4.0" - }, + "license": "MIT", "engines": { - "node": ">= 0.8.0" + "node": ">= 12" } }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, + "license": "MIT", "dependencies": { - "punycode": "^2.1.0" + "json-buffer": "3.0.1" } }, - "node_modules/url-join": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/url-join/-/url-join-2.0.5.tgz", - "integrity": "sha1-WvIvGMBSoACkjXuCxenC4v7tpyg=", - "dev": true - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "node_modules/v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, + "license": "MIT", "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" } }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, + "node_modules/lib0": { + "version": "0.2.115-2", + "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.115-2.tgz", + "integrity": "sha512-LBe5bPJTGG9/7F+1Ax1moAHrHJ1TaaTQWw7J2t6L19yHN3U6uHBSUcIRsews1f6J7fiKWwoiNohGCebd96lnig==", + "license": "MIT", "dependencies": { - "isexe": "^2.0.0" + "isomorphic.js": "^0.2.4" }, "bin": { - "node-which": "bin/node-which" + "0ecdsa-generate-keypair": "bin/0ecdsa-generate-keypair.js", + "0gentesthtml": "bin/gentesthtml.js", + "0serve": "bin/0serve.js" }, "engines": { - "node": ">= 8" + "node": ">=16" + }, + "funding": { + "type": "GitHub Sponsors ❤", + "url": "https://github.com/sponsors/dmonad" } }, - "node_modules/which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", "dev": true, + "license": "MIT", "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "uc.micro": "^2.0.0" } }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "node_modules/load-json-file": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-5.3.0.tgz", + "integrity": "sha512-cJGP40Jc/VXUsp8/OrnyKyTZ1y6v/dphm3bioS+RrKXjK2BB6wHUd6JptZEFDGgGahMT+InnZO5i1Ei9mpC8Bw==", "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.15", + "parse-json": "^4.0.0", + "pify": "^4.0.1", + "strip-bom": "^3.0.0", + "type-fest": "^0.3.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">=6" } }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "node_modules/xdg-basedir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", - "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", + "node_modules/load-json-file/node_modules/type-fest": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz", + "integrity": "sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==", "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">=8" + "node": ">=6" } }, - "node_modules/xmlcreate": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", - "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", - "dev": true - }, - "node_modules/y-protocols": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/y-protocols/-/y-protocols-1.0.5.tgz", - "integrity": "sha512-Wil92b7cGk712lRHDqS4T90IczF6RkcvCwAD0A2OPg+adKmOe+nOiT/N2hvpQIWS3zfjmtL4CPaH5sIW1Hkm/A==", + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, + "license": "MIT", "dependencies": { - "lib0": "^0.2.42" + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" }, "funding": { - "type": "GitHub Sponsors ❤", - "url": "https://github.com/sponsors/dmonad" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", - "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", - "dev": true, - "requires": { - "@babel/highlight": "^7.16.7" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", - "dev": true - }, - "@babel/highlight": { - "version": "7.17.9", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.17.9.tgz", - "integrity": "sha512-J9PfEKCbFIv2X5bjTMiZu6Vf341N05QIY+d6FvVKynkG1S7G0j3I0QoRtWIrXhZ+/Nlb5Q0MzqL7TokEJ5BNHg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.16.7", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.17.9", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.9.tgz", - "integrity": "sha512-vqUSBLP8dQHFPdPi9bc5GK9vRkYHJ49fsZdtoJ8EQ8ibpwk5rPKfvNIwChB0KVXcIjcepEBBd2VHC5r9Gy8ueg==", - "dev": true - }, - "@eslint/eslintrc": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.3.0.tgz", - "integrity": "sha512-1JTKgrOKAHVivSvOYw+sJOunkBjUOvjqWk1DPja7ZFhIS2mX/4EgTT8M7eTK9jrKhL/FvXXEbQwIs3pg1xp3dg==", - "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^12.1.0", - "ignore": "^4.0.6", - "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "lodash": "^4.17.20", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" }, - "@rollup/plugin-commonjs": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-17.1.0.tgz", - "integrity": "sha512-PoMdXCw0ZyvjpCMT5aV4nkL0QywxP29sODQsSGeDpr/oI49Qq9tRtAsb/LbYbDzFlOydVEqHmmZWFtXJEAX9ew==", - "dev": true, - "requires": { - "@rollup/pluginutils": "^3.1.0", - "commondir": "^1.0.1", - "estree-walker": "^2.0.1", - "glob": "^7.1.6", - "is-reference": "^1.2.1", - "magic-string": "^0.25.7", - "resolve": "^1.17.0" - } - }, - "@rollup/plugin-node-resolve": { - "version": "11.2.1", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.2.1.tgz", - "integrity": "sha512-yc2n43jcqVyGE2sqV5/YCmocy9ArjVAP/BeXyTtADTBBX6V0e5UMqwO8CdQ0kzjb6zu5P1qMzsScCMRvE9OlVg==", - "dev": true, - "requires": { - "@rollup/pluginutils": "^3.1.0", - "@types/resolve": "1.17.1", - "builtin-modules": "^3.1.0", - "deepmerge": "^4.2.2", - "is-module": "^1.0.0", - "resolve": "^1.19.0" - } - }, - "@rollup/pluginutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", - "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "dev": true, - "requires": { - "@types/estree": "0.0.39", - "estree-walker": "^1.0.1", - "picomatch": "^2.2.2" - }, + "license": "MIT", "dependencies": { - "estree-walker": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", - "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", - "dev": true - } + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" } }, - "@types/estree": { - "version": "0.0.39", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", - "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", - "dev": true - }, - "@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", - "dev": true - }, - "@types/linkify-it": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.2.tgz", - "integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==", - "dev": true - }, - "@types/markdown-it": { - "version": "12.2.3", - "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", - "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", + "node_modules/lru-cache": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", + "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", "dev": true, - "requires": { - "@types/linkify-it": "*", - "@types/mdurl": "*" + "license": "ISC", + "engines": { + "node": "20 || >=22" } }, - "@types/mdurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", - "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==", - "dev": true - }, - "@types/node": { - "version": "17.0.25", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.25.tgz", - "integrity": "sha512-wANk6fBrUwdpY4isjWrKTufkrXdu1D2YHCot2fD/DfWxF5sMrVSA+KN7ydckvaTCh0HiqX9IVl0L5/ZoXg5M7w==", - "dev": true - }, - "@types/resolve": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", - "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==", + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", "dev": true, - "requires": { - "@types/node": "*" + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" } }, - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true - }, - "acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "node_modules/markdownlint": { + "version": "0.38.0", + "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.38.0.tgz", + "integrity": "sha512-xaSxkaU7wY/0852zGApM8LdlIfGCW8ETZ0Rr62IQtAnUMlMuifsg09vWJcNYeL4f0anvr8Vo4ZQar8jGpV0btQ==", "dev": true, - "requires": {} + "license": "MIT", + "dependencies": { + "micromark": "4.0.2", + "micromark-core-commonmark": "2.0.3", + "micromark-extension-directive": "4.0.0", + "micromark-extension-gfm-autolink-literal": "2.1.0", + "micromark-extension-gfm-footnote": "2.1.0", + "micromark-extension-gfm-table": "2.1.1", + "micromark-extension-math": "3.1.0", + "micromark-util-types": "2.0.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/DavidAnson" + } }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "node_modules/markdownlint-cli": { + "version": "0.45.0", + "resolved": "https://registry.npmjs.org/markdownlint-cli/-/markdownlint-cli-0.45.0.tgz", + "integrity": "sha512-GiWr7GfJLVfcopL3t3pLumXCYs8sgWppjIA1F/Cc3zIMgD3tmkpyZ1xkm1Tej8mw53B93JsDjgA3KOftuYcfOw==", "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "license": "MIT", + "dependencies": { + "commander": "~13.1.0", + "glob": "~11.0.2", + "ignore": "~7.0.4", + "js-yaml": "~4.1.0", + "jsonc-parser": "~3.3.1", + "jsonpointer": "~5.0.1", + "markdown-it": "~14.1.0", + "markdownlint": "~0.38.0", + "minimatch": "~10.0.1", + "run-con": "~1.3.2", + "smol-toml": "~1.3.4" + }, + "bin": { + "markdownlint": "markdownlint.js" + }, + "engines": { + "node": ">=20" } }, - "ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "dev": true, - "requires": { - "color-convert": "^1.9.0" + "license": "MIT", + "engines": { + "node": ">= 0.4" } }, - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true, + "license": "MIT" }, - "array-includes": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.4.tgz", - "integrity": "sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw==", + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1", - "get-intrinsic": "^1.1.1", - "is-string": "^1.0.7" + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-directive": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-4.0.0.tgz", + "integrity": "sha512-/C2nqVmXXmiseSSuCdItCMho7ybwwop6RrrRPk0KbOHW21JKoCldC+8rFOaundDoRBUWBnJJcxeA/Kvi34WQXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "parse-entities": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "array.prototype.flat": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz", - "integrity": "sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw==", + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.2", - "es-shim-unscopables": "^1.0.0" + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "array.prototype.flatmap": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.0.tgz", - "integrity": "sha512-PZC9/8TKAIxcWKdyeb77EzULHPrIX/tIZebLJUQOMR1OwYosT8yggdfWScfTBCDj5utONvOuPQQumYsU2ULbkg==", + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.2", - "es-shim-unscopables": "^1.0.0" + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true - }, - "async": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", "dev": true, - "requires": { - "lodash": "^4.17.14" + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "basic-auth": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.1.0.tgz", - "integrity": "sha1-RSIe5Cn37h5QNb4/UVM/HN/SmIQ=", - "dev": true - }, - "bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "dev": true - }, - "boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", - "dev": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "builtin-modules": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz", - "integrity": "sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==", - "dev": true - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "callsites": { + "node_modules/micromark-extension-math": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "catharsis": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", - "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", + "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-3.1.0.tgz", + "integrity": "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==", "dev": true, - "requires": { - "lodash": "^4.17.15" + "license": "MIT", + "dependencies": { + "@types/katex": "^0.16.0", + "devlop": "^1.0.0", + "katex": "^0.16.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "dependencies": { - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "cheerio": { - "version": "0.22.0", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.22.0.tgz", - "integrity": "sha1-qbqoYKP5tZWmuBsahocxIe06Jp4=", - "dev": true, - "requires": { - "css-select": "~1.2.0", - "dom-serializer": "~0.1.0", - "entities": "~1.1.1", - "htmlparser2": "^3.9.1", - "lodash.assignin": "^4.0.9", - "lodash.bind": "^4.1.4", - "lodash.defaults": "^4.0.1", - "lodash.filter": "^4.4.0", - "lodash.flatten": "^4.2.0", - "lodash.foreach": "^4.3.0", - "lodash.map": "^4.4.0", - "lodash.merge": "^4.4.0", - "lodash.pick": "^4.2.1", - "lodash.reduce": "^4.4.0", - "lodash.reject": "^4.4.0", - "lodash.some": "^4.4.0" - }, - "dependencies": { - "entities": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", - "dev": true + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "dev": true, - "requires": { - "color-name": "1.1.3" + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", - "dev": true - }, - "commander": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.6.0.tgz", - "integrity": "sha1-nfflL7Kgyw+4kFjugMMQQiXzfh0=", - "dev": true - }, - "commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "concurrently": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-3.6.1.tgz", - "integrity": "sha512-/+ugz+gwFSEfTGUxn0KHkY+19XPRTXR8+7oUK/HxgiN1n7FjeJmkrbSiXAJfyQ0zORgJYPaenmymwon51YXH9Q==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "commander": "2.6.0", - "date-fns": "^1.23.0", - "lodash": "^4.5.1", - "read-pkg": "^3.0.0", - "rx": "2.3.24", - "spawn-command": "^0.0.2-1", - "supports-color": "^3.2.3", - "tree-kill": "^1.1.0" - } - }, - "corser": { + "node_modules/micromark-factory-title": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz", - "integrity": "sha1-jtolLsqrWEDc2XXOuQ2TcMgZ/4c=", - "dev": true - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "css-select": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", - "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", "dev": true, - "requires": { - "boolbase": "~1.0.0", - "css-what": "2.1", - "domutils": "1.5.1", - "nth-check": "~1.0.1" + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "css-what": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", - "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==", - "dev": true - }, - "date-fns": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz", - "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==", - "dev": true - }, - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "dev": true, - "requires": { - "ms": "^2.1.1" + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "deep-extend": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.5.1.tgz", - "integrity": "sha512-N8vBdOa+DF7zkRrDCsaOXoCs/E2fJfx9B9MrKnnSiHNh4ws7eSys6YQE4KvT1cecKmOASYQBhbKjeuDD9lT81w==", - "dev": true - }, - "deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", - "dev": true - }, - "define-properties": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", - "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", "dev": true, - "requires": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" } }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", "dev": true, - "requires": { - "esutils": "^2.0.2" + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "dom-serializer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", - "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", "dev": true, - "requires": { - "domelementtype": "^1.3.0", - "entities": "^1.1.1" - }, - "dependencies": { - "entities": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", - "dev": true + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "domelementtype": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", - "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", - "dev": true - }, - "domhandler": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", - "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", "dev": true, - "requires": { - "domelementtype": "1" + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" } }, - "domutils": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", - "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", "dev": true, - "requires": { - "dom-serializer": "0", - "domelementtype": "1" - } + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" }, - "ecstatic": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/ecstatic/-/ecstatic-3.3.2.tgz", - "integrity": "sha512-fLf9l1hnwrHI2xn9mEDT7KIi22UDqA2jaCwyCbSUJh9a1V+LEUSL/JO/6TIz/QyuBURWUHrFL5Kg2TtO1bkkog==", + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", "dev": true, - "requires": { - "he": "^1.1.1", - "mime": "^1.6.0", - "minimist": "^1.1.0", - "url-join": "^2.0.5" - } - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" }, - "enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", "dev": true, - "requires": { - "ansi-colors": "^4.1.1" + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" } }, - "entities": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", - "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", - "dev": true - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "es-abstract": { - "version": "1.19.5", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.5.tgz", - "integrity": "sha512-Aa2G2+Rd3b6kxEUKTF4TaW67czBLyAv3z7VOhYRU50YBx+bbsYZ9xQP4lMNazePuFlybXI0V4MruPos7qUo5fA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "get-intrinsic": "^1.1.1", - "get-symbol-description": "^1.0.0", - "has": "^1.0.3", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.3", - "is-callable": "^1.2.4", - "is-negative-zero": "^2.0.2", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "is-string": "^1.0.7", - "is-weakref": "^1.0.2", - "object-inspect": "^1.12.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "string.prototype.trimend": "^1.0.4", - "string.prototype.trimstart": "^1.0.4", - "unbox-primitive": "^1.0.1" + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" } }, - "es-shim-unscopables": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", - "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", "dev": true, - "requires": { - "has": "^1.0.3" + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" } }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "eslint": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.18.0.tgz", - "integrity": "sha512-fbgTiE8BfUJZuBeq2Yi7J3RB3WGUQ9PNuNbmgi6jt9Iv8qrkxfy19Ds3OpL1Pm7zg3BtTVhvcUZbIRQ0wmSjAQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@eslint/eslintrc": "^0.3.0", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "enquirer": "^2.3.5", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", - "esquery": "^1.2.0", - "esutils": "^2.0.2", - "file-entry-cache": "^6.0.0", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.0.0", - "globals": "^12.1.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash": "^4.17.20", - "minimatch": "^3.0.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "table": "^6.0.4", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "eslint-config-standard": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-16.0.3.tgz", - "integrity": "sha512-x4fmJL5hGqNJKGHSjnLdgA6U6h1YW/G2dW9fA+cyVur4SK6lyue8+UgNKWlZtUDTXvgKDD/Oa3GQjmB5kjtVvg==", + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "dev": true, - "requires": {} + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" }, - "eslint-config-standard-jsx": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-standard-jsx/-/eslint-config-standard-jsx-10.0.0.tgz", - "integrity": "sha512-hLeA2f5e06W1xyr/93/QJulN/rLbUVUmqTlexv9PRKHFwEC9ffJcH2LvJhMoEqYQBEYafedgGZXH2W8NUpt5lA==", + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", "dev": true, - "requires": {} + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" }, - "eslint-import-resolver-node": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", - "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", + "node_modules/minimatch": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", + "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", "dev": true, - "requires": { - "debug": "^3.2.7", - "resolve": "^1.20.0" + "license": "ISC", + "dependencies": { + "@isaacs/brace-expansion": "^5.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "eslint-module-utils": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.3.tgz", - "integrity": "sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==", + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true, - "requires": { - "debug": "^3.2.7", - "find-up": "^2.1.0" + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "eslint-plugin-es": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", - "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, - "requires": { - "eslint-utils": "^2.0.0", - "regexpp": "^3.0.0" + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" } }, - "eslint-plugin-import": { - "version": "2.24.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.24.2.tgz", - "integrity": "sha512-hNVtyhiEtZmpsabL4neEj+6M5DCLgpYyG9nzJY8lZQeQXEn5UPW1DpUdsMHMXsq98dbNm7nt1w9ZMSVpfJdi8Q==", + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, - "requires": { - "array-includes": "^3.1.3", - "array.prototype.flat": "^1.2.4", - "debug": "^2.6.9", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.6", - "eslint-module-utils": "^2.6.2", - "find-up": "^2.0.0", - "has": "^1.0.3", - "is-core-module": "^2.6.0", - "minimatch": "^3.0.4", - "object.values": "^1.1.4", - "pkg-up": "^2.0.0", - "read-pkg-up": "^3.0.0", - "resolve": "^1.20.0", - "tsconfig-paths": "^3.11.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" } }, - "eslint-plugin-node": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", - "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "dev": true, - "requires": { - "eslint-plugin-es": "^3.0.0", - "eslint-utils": "^2.0.0", - "ignore": "^5.1.1", - "minimatch": "^3.0.4", - "resolve": "^1.10.1", - "semver": "^6.1.0" + "license": "MIT", + "engines": { + "node": ">= 0.4" }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "eslint-plugin-promise": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-5.1.1.tgz", - "integrity": "sha512-XgdcdyNzHfmlQyweOPTxmc7pIsS6dE4MvwhXWMQ2Dxs1XAL2GJDilUsjWen6TWik0aSI+zD/PqocZBblcm9rdA==", + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "dev": true, - "requires": {} + "license": "MIT", + "engines": { + "node": ">= 0.4" + } }, - "eslint-plugin-react": { - "version": "7.25.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.25.3.tgz", - "integrity": "sha512-ZMbFvZ1WAYSZKY662MBVEWR45VaBT6KSJCiupjrNlcdakB90juaZeDCbJq19e73JZQubqFtgETohwgAt8u5P6w==", + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", "dev": true, - "requires": { - "array-includes": "^3.1.3", - "array.prototype.flatmap": "^1.2.4", - "doctrine": "^2.1.0", - "estraverse": "^5.2.0", - "jsx-ast-utils": "^2.4.1 || ^3.0.0", - "minimatch": "^3.0.4", - "object.entries": "^1.1.4", - "object.fromentries": "^2.0.4", - "object.hasown": "^1.0.0", - "object.values": "^1.1.4", - "prop-types": "^15.7.2", - "resolve": "^2.0.0-next.3", - "string.prototype.matchall": "^4.0.5" - }, - "dependencies": { - "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "resolve": { - "version": "2.0.0-next.3", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.3.tgz", - "integrity": "sha512-W8LucSynKUIDu9ylraa7ueVZ7hc0uAgJBxVsQSKOXOyle8a93qXhcz+XAXZ8bIq2d6i4Ehddn6Evt+0/UwKk6Q==", - "dev": true, - "requires": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" - } - } + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, + "license": "MIT", "dependencies": { - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - } + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" } }, - "eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" - }, + "license": "MIT", "dependencies": { - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true - }, - "espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", "dev": true, - "requires": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" - }, + "license": "MIT", "dependencies": { - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" } }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "esquery": { + "node_modules/once": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, - "requires": { - "estraverse": "^5.1.0" + "license": "ISC", + "dependencies": { + "wrappy": "1" } }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, - "requires": { - "estraverse": "^5.2.0" + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" } }, - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - }, - "estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", "dev": true, - "requires": { - "flat-cache": "^3.0.4" + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, - "requires": { - "locate-path": "^2.0.0" + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, - "requires": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "flatted": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", - "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", - "dev": true - }, - "follow-redirects": { - "version": "1.14.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", - "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==", - "dev": true - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true, - "optional": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "license": "MIT", + "engines": { + "node": ">=6" + } }, - "functional-red-black-tree": { + "node_modules/package-json-from-dist": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, - "functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true - }, - "get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" - } - }, - "get-stdin": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz", - "integrity": "sha1-Ei4WFZHiH/TFJTAwVpPyDmOTo5g=", - "dev": true + "license": "BlueOak-1.0.0" }, - "get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" } }, - "glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", "dev": true, - "requires": { - "is-glob": "^4.0.1" + "license": "MIT", + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" } }, - "globals": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", - "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, - "requires": { - "type-fest": "^0.8.1" + "license": "MIT", + "engines": { + "node": ">=8" } }, - "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true - }, - "graceful-readlink": { + "node_modules/path-is-absolute": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", - "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", - "dev": true - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "dev": true, - "requires": { - "function-bind": "^1.1.1" + "license": "MIT", + "engines": { + "node": ">=0.10.0" } }, - "has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dev": true - }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, - "requires": { - "get-intrinsic": "^1.1.1" + "license": "MIT", + "engines": { + "node": ">=8" } }, - "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" }, - "has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "node_modules/path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", "dev": true, - "requires": { - "has-symbols": "^1.0.2" + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true - }, - "hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, - "htmlparser2": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", - "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", - "dev": true, - "requires": { - "domelementtype": "^1.3.1", - "domhandler": "^2.3.0", - "domutils": "^1.5.1", - "entities": "^1.1.1", - "inherits": "^2.0.1", - "readable-stream": "^3.1.1" - }, - "dependencies": { - "entities": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", - "dev": true - } + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" } }, - "http-proxy": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", - "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "node_modules/pkg-conf": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-3.1.0.tgz", + "integrity": "sha512-m0OTbR/5VPNPqO1ph6Fqbj7Hv6QU7gR/tQW40ZqrL1rjgCU85W6C1bJn0BItuJqnR98PWzw7Z8hHeChD1WrgdQ==", "dev": true, - "requires": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" + "license": "MIT", + "dependencies": { + "find-up": "^3.0.0", + "load-json-file": "^5.2.0" + }, + "engines": { + "node": ">=6" } }, - "http-server": { - "version": "0.12.3", - "resolved": "https://registry.npmjs.org/http-server/-/http-server-0.12.3.tgz", - "integrity": "sha512-be0dKG6pni92bRjq0kvExtj/NrrAd28/8fCXkaI/4piTwQMSDSLMhWyW0NI1V+DBI3aa1HMlQu46/HjVLfmugA==", + "node_modules/pkg-conf/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, - "requires": { - "basic-auth": "^1.0.3", - "colors": "^1.4.0", - "corser": "^2.0.1", - "ecstatic": "^3.3.2", - "http-proxy": "^1.18.0", - "minimist": "^1.2.5", - "opener": "^1.5.1", - "portfinder": "^1.0.25", - "secure-compare": "3.0.1", - "union": "~0.5.0" + "license": "MIT", + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" } }, - "ignore": { - "version": "5.1.9", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.9.tgz", - "integrity": "sha512-2zeMQpbKz5dhZ9IwL0gbxSW5w0NK/MSAMtNuhgIHEPmaU3vPdKPL0UdvUCXs5SS4JAwsBxysK5sFMW8ocFiVjQ==", - "dev": true - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "node_modules/pkg-conf/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" + "license": "MIT", + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" } }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "node_modules/pkg-conf/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true - }, - "internal-slot": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", - "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "node_modules/pkg-conf/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "dev": true, - "requires": { - "get-intrinsic": "^1.1.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" + "license": "MIT", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" } }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "node_modules/pkg-conf/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", "dev": true, - "requires": { - "has-bigints": "^1.0.1" + "license": "MIT", + "engines": { + "node": ">=4" } }, - "is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "license": "MIT", + "engines": { + "node": ">= 0.4" } }, - "is-callable": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", - "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", - "dev": true - }, - "is-core-module": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", - "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, - "requires": { - "has": "^1.0.3" + "license": "MIT", + "engines": { + "node": ">= 0.8.0" } }, - "is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" } }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", "dev": true, - "requires": { - "is-extglob": "^2.1.1" + "license": "MIT", + "engines": { + "node": ">=6" } }, - "is-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", - "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=", - "dev": true + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" }, - "is-negative-zero": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", - "dev": true + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "license": "MIT" }, - "is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "is-reference": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", - "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", "dev": true, - "requires": { - "@types/estree": "*" + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" } }, - "is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, - "requires": { - "call-bind": "^1.0.2" + "license": "MIT", + "engines": { + "node": ">=0.10.0" } }, - "is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, - "requires": { - "has-symbols": "^1.0.2" + "license": "MIT", + "engines": { + "node": ">=4" } }, - "is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", "dev": true, - "requires": { - "call-bind": "^1.0.2" + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" } }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "isomorphic.js": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.2.5.tgz", - "integrity": "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==" - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "dependencies": { - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - } + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "js2xmlparser": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", - "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", - "dev": true, - "requires": { - "xmlcreate": "^2.0.4" - } - }, - "jsdoc": { - "version": "3.6.10", - "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.10.tgz", - "integrity": "sha512-IdQ8ppSo5LKZ9o3M+LKIIK8i00DIe5msDvG3G81Km+1dhy0XrOWD0Ji8H61ElgyEj/O9KRLokgKbAM9XX9CJAg==", - "dev": true, - "requires": { - "@babel/parser": "^7.9.4", - "@types/markdown-it": "^12.2.3", - "bluebird": "^3.7.2", - "catharsis": "^0.9.0", - "escape-string-regexp": "^2.0.0", - "js2xmlparser": "^4.0.2", - "klaw": "^4.0.1", - "markdown-it": "^12.3.2", - "markdown-it-anchor": "^8.4.1", - "marked": "^4.0.10", - "mkdirp": "^1.0.4", - "requizzle": "^0.2.3", - "strip-json-comments": "^3.1.0", - "taffydb": "2.6.2", - "underscore": "~1.13.2" - }, - "dependencies": { - "escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true - } + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "requires": { - "minimist": "^1.2.0" + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" } }, - "jsonc-parser": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.2.1.tgz", - "integrity": "sha512-o6/yDBYccGvTz1+QFevz6l6OBZ2+fMVu2JZ9CIhzsYRX4mjaK5IyX9eldUdCmga16zlgQxyrj5pt9kzuj2C02w==", - "dev": true + "node_modules/rollup": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.5.tgz", + "integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.52.5", + "@rollup/rollup-android-arm64": "4.52.5", + "@rollup/rollup-darwin-arm64": "4.52.5", + "@rollup/rollup-darwin-x64": "4.52.5", + "@rollup/rollup-freebsd-arm64": "4.52.5", + "@rollup/rollup-freebsd-x64": "4.52.5", + "@rollup/rollup-linux-arm-gnueabihf": "4.52.5", + "@rollup/rollup-linux-arm-musleabihf": "4.52.5", + "@rollup/rollup-linux-arm64-gnu": "4.52.5", + "@rollup/rollup-linux-arm64-musl": "4.52.5", + "@rollup/rollup-linux-loong64-gnu": "4.52.5", + "@rollup/rollup-linux-ppc64-gnu": "4.52.5", + "@rollup/rollup-linux-riscv64-gnu": "4.52.5", + "@rollup/rollup-linux-riscv64-musl": "4.52.5", + "@rollup/rollup-linux-s390x-gnu": "4.52.5", + "@rollup/rollup-linux-x64-gnu": "4.52.5", + "@rollup/rollup-linux-x64-musl": "4.52.5", + "@rollup/rollup-openharmony-arm64": "4.52.5", + "@rollup/rollup-win32-arm64-msvc": "4.52.5", + "@rollup/rollup-win32-ia32-msvc": "4.52.5", + "@rollup/rollup-win32-x64-gnu": "4.52.5", + "@rollup/rollup-win32-x64-msvc": "4.52.5", + "fsevents": "~2.3.2" + } }, - "jsx-ast-utils": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.2.tgz", - "integrity": "sha512-HDAyJ4MNQBboGpUnHAVUNJs6X0lh058s6FuixsFGP7MgJYpD6Vasd6nzSG5iIfXu1zAYlHJ/zsOKNlrenTUBnw==", + "node_modules/run-con": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/run-con/-/run-con-1.3.2.tgz", + "integrity": "sha512-CcfE+mYiTcKEzg0IqS08+efdnH0oJ3zV0wSUFBNrMHMuxCtXvBCLzCJHatwuXDcu/RlhjTziTo/a1ruQik6/Yg==", "dev": true, - "requires": { - "array-includes": "^3.1.4", - "object.assign": "^4.1.2" + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~4.1.0", + "minimist": "^1.2.8", + "strip-json-comments": "~3.1.1" + }, + "bin": { + "run-con": "cli.js" } }, - "klaw": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-4.0.1.tgz", - "integrity": "sha512-pgsE40/SvC7st04AHiISNewaIMUbY5V/K8b21ekiPiFoYs/EYSdsGa+FJArB1d441uq4Q8zZyIxvAzkGNlBdRw==", - "dev": true + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } }, - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", "dev": true, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" } }, - "lib0": { - "version": "0.2.51", - "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.51.tgz", - "integrity": "sha512-05Erb3465CxJa38LQlMz4EbetNvRna1S3BzqEjC0/pmp5cQuQSfNNmeS0722Wev1dRlMUp2Cql0gQ55krSXf2Q==", - "requires": { - "isomorphic.js": "^0.2.4" + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "linkify-it": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", - "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", "dev": true, - "requires": { - "uc.micro": "^1.0.1" + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" + "license": "ISC", + "bin": { + "semver": "bin/semver.js" } }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } }, - "lodash.assignin": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz", - "integrity": "sha1-uo31+4QesKPoBEIysOJjqNxqKKI=", - "dev": true + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } }, - "lodash.bind": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-4.2.1.tgz", - "integrity": "sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU=", - "dev": true + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } }, - "lodash.defaults": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=", - "dev": true + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } }, - "lodash.differencewith": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.differencewith/-/lodash.differencewith-4.5.0.tgz", - "integrity": "sha1-uvr7yRi1UVTheRdqALsK76rIVLc=", - "dev": true - }, - "lodash.filter": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz", - "integrity": "sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4=", - "dev": true - }, - "lodash.flatten": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=", - "dev": true - }, - "lodash.foreach": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz", - "integrity": "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM=", - "dev": true + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "lodash.map": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", - "integrity": "sha1-dx7Hg540c9nEzeKLGTlMNWL09tM=", - "dev": true + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "lodash.pick": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", - "integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=", - "dev": true - }, - "lodash.reduce": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", - "integrity": "sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs=", - "dev": true - }, - "lodash.reject": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.reject/-/lodash.reject-4.6.0.tgz", - "integrity": "sha1-gNZJLcFHCGS79YNTO2UfQqn1JBU=", - "dev": true - }, - "lodash.some": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", - "integrity": "sha1-G7nzFO9ri63tE7VJFpsqlF62jk0=", - "dev": true - }, - "lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", - "dev": true - }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "dev": true, - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", "dev": true, - "requires": { - "yallist": "^4.0.0" + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "magic-string": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", - "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "dev": true, - "requires": { - "sourcemap-codec": "^1.4.8" + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "markdown-it": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", - "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "dev": true, - "requires": { - "argparse": "^2.0.1", - "entities": "~2.1.0", - "linkify-it": "^3.0.1", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "markdown-it-anchor": { - "version": "8.6.2", - "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.2.tgz", - "integrity": "sha512-JNaekTlIwwyYGBN3zifZDxgz4bSL8sbEj58fdTZGmPSMMGXBZapFjcZk2I33Jy79c1fvCKHpF7MA/67FOTjvzA==", + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, - "requires": {} + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, - "markdownlint": { - "version": "0.20.4", - "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.20.4.tgz", - "integrity": "sha512-jpfaPgjT0OpeBbemjYNZbzGG3hCLcAIvrm/pEY3+q/szDScG6ZonDacqySVRJAv9glbo8y4wBPJ0wgW17+9GGA==", + "node_modules/smol-toml": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.3.4.tgz", + "integrity": "sha512-UOPtVuYkzYGee0Bd2Szz8d2G3RfMfJ2t3qVdZUAozZyAk+a0Sxa+QKix0YCwjL/A1RR0ar44nCxaoN9FxdJGwA==", "dev": true, - "requires": { - "markdown-it": "10.0.0" + "license": "BSD-3-Clause", + "engines": { + "node": ">= 18" }, - "dependencies": { - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "entities": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz", - "integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==", - "dev": true + "funding": { + "url": "https://github.com/sponsors/cyyynthia" + } + }, + "node_modules/standard": { + "version": "17.1.2", + "resolved": "https://registry.npmjs.org/standard/-/standard-17.1.2.tgz", + "integrity": "sha512-WLm12WoXveKkvnPnPnaFUUHuOB2cUdAsJ4AiGHL2G0UNMrcRAWY2WriQaV8IQ3oRmYr0AWUbLNr94ekYFAHOrA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" }, - "linkify-it": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz", - "integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==", - "dev": true, - "requires": { - "uc.micro": "^1.0.1" - } + { + "type": "patreon", + "url": "https://www.patreon.com/feross" }, - "markdown-it": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-10.0.0.tgz", - "integrity": "sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "entities": "~2.0.0", - "linkify-it": "^2.0.0", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" - } + { + "type": "consulting", + "url": "https://feross.org/support" } + ], + "license": "MIT", + "dependencies": { + "eslint": "^8.41.0", + "eslint-config-standard": "17.1.0", + "eslint-config-standard-jsx": "^11.0.0", + "eslint-plugin-import": "^2.27.5", + "eslint-plugin-n": "^15.7.0", + "eslint-plugin-promise": "^6.1.1", + "eslint-plugin-react": "^7.36.1", + "standard-engine": "^15.1.0", + "version-guard": "^1.1.1" + }, + "bin": { + "standard": "bin/cmd.cjs" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "markdownlint-cli": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/markdownlint-cli/-/markdownlint-cli-0.23.2.tgz", - "integrity": "sha512-OSl5OZ8xzGN6z355cqRkiq67zPi3reJimklaF72p0554q85Dng5ToOjjSB9tDKZebSt85jX8cp+ruoQlPqOsPA==", - "dev": true, - "requires": { - "commander": "~2.9.0", - "deep-extend": "~0.5.1", - "get-stdin": "~5.0.1", - "glob": "~7.1.2", - "ignore": "~5.1.4", - "js-yaml": "~3.13.1", - "jsonc-parser": "~2.2.0", - "lodash.differencewith": "~4.5.0", - "lodash.flatten": "~4.4.0", - "markdownlint": "~0.20.4", - "markdownlint-rule-helpers": "~0.11.0", - "minimatch": "~3.0.4", - "minimist": "~1.2.5", - "rc": "~1.2.7" - }, - "dependencies": { - "commander": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", - "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", - "dev": true, - "requires": { - "graceful-readlink": ">= 1.0.0" - } + "node_modules/standard-engine": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/standard-engine/-/standard-engine-15.1.0.tgz", + "integrity": "sha512-VHysfoyxFu/ukT+9v49d4BRXIokFRZuH3z1VRxzFArZdjSCFpro6rEIU3ji7e4AoAtuSfKBkiOmsrDqKW5ZSRw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" }, - "glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } + { + "type": "patreon", + "url": "https://www.patreon.com/feross" }, - "minimatch": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", - "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } + { + "type": "consulting", + "url": "https://feross.org/support" } + ], + "license": "MIT", + "dependencies": { + "get-stdin": "^8.0.0", + "minimist": "^1.2.6", + "pkg-conf": "^3.1.0", + "xdg-basedir": "^4.0.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "markdownlint-rule-helpers": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/markdownlint-rule-helpers/-/markdownlint-rule-helpers-0.11.0.tgz", - "integrity": "sha512-PhGii9dOiDJDXxiRMpK8N0FM9powprvRPsXALgkjlSPTwLh6ymH+iF3iUe3nq8KGu26tclFBlLL5xAGy/zb7FA==", - "dev": true - }, - "marked": { - "version": "4.0.14", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.14.tgz", - "integrity": "sha512-HL5sSPE/LP6U9qKgngIIPTthuxC0jrfxpYMZ3LdGDD3vTnLs59m2Z7r6+LNDR3ToqEQdkKd6YaaEfJhodJmijQ==", - "dev": true - }, - "mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=", - "dev": true - }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", "dev": true, - "requires": { - "brace-expansion": "^1.1.7" + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" } }, - "minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", - "dev": true - }, - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" } }, - "nth-check": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", - "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, - "requires": { - "boolbase": "~1.0.0" + "license": "MIT", + "engines": { + "node": ">=8" } }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" }, - "object-inspect": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", - "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==", - "dev": true + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", "dev": true, - "requires": { - "call-bind": "^1.0.0", + "license": "MIT", + "dependencies": { "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" + "es-abstract": "^1.17.5" } }, - "object.entries": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.5.tgz", - "integrity": "sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g==", + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "object.fromentries": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.5.tgz", - "integrity": "sha512-CAyG5mWQRRiBU57Re4FKoTBjXfDoNwdFVH2Y1tS9PqCsfUTymAohOkEMSG3aRNKmv4lV3O7p1et7c187q6bynw==", + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "object.hasown": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.0.tgz", - "integrity": "sha512-MhjYRfj3GBlhSkDHo6QmvgjRLXQ2zndabdf3nX0yTyZK9rPfxb6uRpAac8HXNLy1GpqWtZ81Qh4v3uOls2sRAg==", + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "object.values": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", - "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, - "requires": { - "wrappy": "1" + "license": "MIT", + "engines": { + "node": ">=8" } }, - "opener": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", - "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", - "dev": true - }, - "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "dev": true, - "requires": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "license": "MIT", + "engines": { + "node": ">=4" } }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, - "requires": { - "p-try": "^1.0.0" + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, - "requires": { - "p-limit": "^1.1.0" + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "p-try": { + "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true, - "requires": { - "callsites": "^3.0.0" + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true, - "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true + "license": "MIT" }, - "path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", "dev": true, - "requires": { - "pify": "^3.0.0" + "license": "MIT", + "bin": { + "tree-kill": "cli.js" } }, - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - }, - "pkg-conf": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-3.1.0.tgz", - "integrity": "sha512-m0OTbR/5VPNPqO1ph6Fqbj7Hv6QU7gR/tQW40ZqrL1rjgCU85W6C1bJn0BItuJqnR98PWzw7Z8hHeChD1WrgdQ==", + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", "dev": true, - "requires": { - "find-up": "^3.0.0", - "load-json-file": "^5.2.0" - }, + "license": "MIT", "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "load-json-file": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-5.3.0.tgz", - "integrity": "sha512-cJGP40Jc/VXUsp8/OrnyKyTZ1y6v/dphm3bioS+RrKXjK2BB6wHUd6JptZEFDGgGahMT+InnZO5i1Ei9mpC8Bw==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.15", - "parse-json": "^4.0.0", - "pify": "^4.0.1", - "strip-bom": "^3.0.0", - "type-fest": "^0.3.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true - }, - "type-fest": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz", - "integrity": "sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==", - "dev": true - } + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" } }, - "pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz", - "integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=", + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true, - "requires": { - "find-up": "^2.1.0" - } + "license": "0BSD" }, - "portfinder": { - "version": "1.0.28", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", - "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, - "requires": { - "async": "^2.6.2", - "debug": "^3.1.1", - "mkdirp": "^0.5.5" - }, + "license": "MIT", "dependencies": { - "mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "requires": { - "minimist": "^1.2.6" - } - } + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" } }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true - }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true - }, - "prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, - "requires": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "qs": { - "version": "6.10.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", - "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", "dev": true, - "requires": { - "side-channel": "^1.0.4" + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" } }, - "rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", "dev": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, + "license": "MIT", "dependencies": { - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true - } + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true - }, - "read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", "dev": true, - "requires": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "read-pkg-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", - "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", "dev": true, - "requires": { - "find-up": "^2.0.0", - "read-pkg": "^3.0.0" + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" } }, - "regexp.prototype.flags": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", - "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "functions-have-names": "^1.2.2" - } - }, - "regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true - }, - "require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true + "license": "MIT" }, - "requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", - "dev": true - }, - "requizzle": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.3.tgz", - "integrity": "sha512-YanoyJjykPxGHii0fZP0uUPEXpvqfBDxWV7s6GKAiiOsiqhX6vHNyW3Qzdmqp/iq/ExbhaGbVrjB4ruEVSM4GQ==", + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", "dev": true, - "requires": { - "lodash": "^4.17.14" + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "resolve": { - "version": "1.22.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", - "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "dev": true, - "requires": { - "is-core-module": "^2.8.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true + "license": "MIT" }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, - "requires": { - "glob": "^7.1.3" + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" } }, - "rollup": { - "version": "2.70.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.70.2.tgz", - "integrity": "sha512-EitogNZnfku65I1DD5Mxe8JYRUCy0hkK5X84IlDtUs+O6JRMpRciXTzyCUuX11b5L5pvjH+OmFXiQ3XjabcXgg==", + "node_modules/version-guard": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/version-guard/-/version-guard-1.1.3.tgz", + "integrity": "sha512-JwPr6erhX53EWH/HCSzfy1tTFrtPXUe927wdM1jqBBeYp1OM+qPHjWbsvv6pIBduqdgxxS+ScfG7S28pzyr2DQ==", "dev": true, - "requires": { - "fsevents": "~2.3.2" + "license": "0BSD", + "engines": { + "node": ">=0.10.48" } }, - "rx": { - "version": "2.3.24", - "resolved": "https://registry.npmjs.org/rx/-/rx-2.3.24.tgz", - "integrity": "sha1-FPlQpCF9fjXapxu8vljv9o6ksrc=", - "dev": true - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true - }, - "secure-compare": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz", - "integrity": "sha1-8aAymzCLIh+uN7mXTz1XjQypmeM=", - "dev": true + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", "dev": true, - "requires": { - "shebang-regex": "^3.0.0" + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", "dev": true, - "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - } + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "dev": true - }, - "spawn-command": { - "version": "0.0.2-1", - "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2-1.tgz", - "integrity": "sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A=", - "dev": true - }, - "spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" + "license": "MIT", + "engines": { + "node": ">=0.10.0" } }, - "spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true - }, - "spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "spdx-license-ids": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz", - "integrity": "sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g==", - "dev": true - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "standard": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/standard/-/standard-16.0.4.tgz", - "integrity": "sha512-2AGI874RNClW4xUdM+bg1LRXVlYLzTNEkHmTG5mhyn45OhbgwA+6znowkOGYy+WMb5HRyELvtNy39kcdMQMcYQ==", - "dev": true, - "requires": { - "eslint": "~7.18.0", - "eslint-config-standard": "16.0.3", - "eslint-config-standard-jsx": "10.0.0", - "eslint-plugin-import": "~2.24.2", - "eslint-plugin-node": "~11.1.0", - "eslint-plugin-promise": "~5.1.0", - "eslint-plugin-react": "~7.25.1", - "standard-engine": "^14.0.1" - } - }, - "standard-engine": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/standard-engine/-/standard-engine-14.0.1.tgz", - "integrity": "sha512-7FEzDwmHDOGva7r9ifOzD3BGdTbA7ujJ50afLVdW/tK14zQEptJjbFuUfn50irqdHDcTbNh0DTIoMPynMCXb0Q==", - "dev": true, - "requires": { - "get-stdin": "^8.0.0", - "minimist": "^1.2.5", - "pkg-conf": "^3.1.0", - "xdg-basedir": "^4.0.0" - }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", "dependencies": { - "get-stdin": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz", - "integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==", - "dev": true - } + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, - "requires": { - "safe-buffer": "~5.2.0" + "license": "MIT", + "engines": { + "node": ">=8" } }, - "string-width": { + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" } }, - "string.prototype.matchall": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz", - "integrity": "sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1", - "get-intrinsic": "^1.1.1", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.3", - "regexp.prototype.flags": "^1.4.1", - "side-channel": "^1.0.4" - } - }, - "string.prototype.trimend": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", - "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "string.prototype.trimstart": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", - "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "strip-ansi": { + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, - "requires": { - "has-flag": "^1.0.0" + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" }, - "table": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/table/-/table-6.8.0.tgz", - "integrity": "sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==", + "node_modules/xdg-basedir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", + "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", "dev": true, - "requires": { - "ajv": "^8.0.1", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "dependencies": { - "ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - } + "license": "MIT", + "engines": { + "node": ">=8" } }, - "taffydb": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", - "integrity": "sha1-fLy2S1oUG2ou/CxdLGe04VCyomg=", - "dev": true - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", - "dev": true - }, - "tsconfig-paths": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", - "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, - "requires": { - "@types/json5": "^0.0.29", - "json5": "^1.0.1", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" + "license": "ISC", + "engines": { + "node": ">=10" } }, - "tui-jsdoc-template": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tui-jsdoc-template/-/tui-jsdoc-template-1.2.2.tgz", - "integrity": "sha512-oqw0IYaot86VJ2owKBozJnilgta0Z55x8r9PeHj7vb+jDoSvJGRUQUcgs56SZh9HE20fx54Pe75p84X85/ygLA==", + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, - "requires": { - "cheerio": "^0.22.0" + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" } }, - "type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, - "requires": { - "prelude-ls": "^1.2.1" + "license": "ISC", + "engines": { + "node": ">=12" } }, - "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true - }, - "typescript": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz", - "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==", - "dev": true - }, - "uc.micro": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", - "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", - "dev": true - }, - "unbox-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", - "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has-bigints": "^1.0.1", - "has-symbols": "^1.0.2", - "which-boxed-primitive": "^1.0.2" + "license": "MIT", + "engines": { + "node": ">=8" } }, - "underscore": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.2.tgz", - "integrity": "sha512-ekY1NhRzq0B08g4bGuX4wd2jZx5GnKz6mKSqFL4nqBlfyMGiG10gDFhDTMEfYmDL6Jy0FUIZp7wiRB+0BP7J2g==", - "dev": true - }, - "union": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz", - "integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==", + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, - "requires": { - "qs": "^6.4.0" - } + "license": "MIT" }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, - "requires": { - "punycode": "^2.1.0" + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" } }, - "url-join": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/url-join/-/url-join-2.0.5.tgz", - "integrity": "sha1-WvIvGMBSoACkjXuCxenC4v7tpyg=", - "dev": true - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "node_modules/yjs": { + "version": "13.6.27", + "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.27.tgz", + "integrity": "sha512-OIDwaflOaq4wC6YlPBy2L6ceKeKuF7DeTxx+jPzv1FHn9tCZ0ZwSRnUBxD05E3yed46fv/FWJbvR+Ud7x0L7zw==", "dev": true, - "requires": { - "isexe": "^2.0.0" + "license": "MIT", + "peer": true, + "dependencies": { + "lib0": "^0.2.99" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=8.0.0" + }, + "funding": { + "type": "GitHub Sponsors ❤", + "url": "https://github.com/sponsors/dmonad" } }, - "which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "node_modules/yjs/node_modules/lib0": { + "version": "0.2.114", + "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.114.tgz", + "integrity": "sha512-gcxmNFzA4hv8UYi8j43uPlQ7CGcyMJ2KQb5kZASw6SnAKAf10hK12i2fjrS3Cl/ugZa5Ui6WwIu1/6MIXiHttQ==", "dev": true, - "requires": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" + "license": "MIT", + "peer": true, + "dependencies": { + "isomorphic.js": "^0.2.4" + }, + "bin": { + "0ecdsa-generate-keypair": "bin/0ecdsa-generate-keypair.js", + "0gentesthtml": "bin/gentesthtml.js", + "0serve": "bin/0serve.js" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "type": "GitHub Sponsors ❤", + "url": "https://github.com/sponsors/dmonad" } }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "xdg-basedir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", - "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", - "dev": true - }, - "xmlcreate": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", - "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", - "dev": true - }, - "y-protocols": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/y-protocols/-/y-protocols-1.0.5.tgz", - "integrity": "sha512-Wil92b7cGk712lRHDqS4T90IczF6RkcvCwAD0A2OPg+adKmOe+nOiT/N2hvpQIWS3zfjmtL4CPaH5sIW1Hkm/A==", + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, - "requires": { - "lib0": "^0.2.42" + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true } } } diff --git a/package.json b/package.json index 83a3db73f..62feded07 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "yjs", - "version": "13.5.41", + "version": "14.0.0-10", "description": "Shared Editing Library", "main": "./dist/yjs.cjs", "module": "./dist/yjs.mjs", @@ -12,36 +12,45 @@ "url": "https://github.com/sponsors/dmonad" }, "scripts": { - "test": "npm run dist && node ./dist/tests.cjs --repetition-time 50", - "test-extensive": "npm run lint && npm run dist && node ./dist/tests.cjs --production --repetition-time 10000", - "dist": "rm -rf dist && rollup -c && tsc", + "clean": "rm -rf dist", + "test": "NODE_ENV=development node ./tests/index.js --repetition-time 50", + "test-extensive": "node ./tests/index.js --production --repetition-time 10000", + "dist": "npm run clean && rollup -c && tsc --skipLibCheck", "watch": "rollup -wc", "lint": "markdownlint README.md && standard && tsc", - "docs": "rm -rf docs; jsdoc --configure ./.jsdoc.json --verbose --readme ./README.md --package ./package.json || true", - "serve-docs": "npm run docs && http-server ./docs/", - "preversion": "npm run lint && PRODUCTION=1 npm run dist && npm run docs && node ./dist/tests.cjs --repetition-time 1000 && test -e dist/src/index.d.ts && test -e dist/yjs.cjs && test -e dist/yjs.cjs", - "debug": "concurrently 'http-server -o test.html' 'npm run watch'", - "trace-deopt": "clear && rollup -c && node --trace-deopt dist/test.cjs", - "trace-opt": "clear && rollup -c && node --trace-opt dist/test.cjs" + "preversion": "PRODUCTION=1 npm run dist && test -e dist/src/index.d.ts && test -e dist/yjs.cjs && test -e dist/yjs.cjs", + "debug": "npm run gentesthtml && 0serve -o test.html", + "trace-deopt": "clear && node --trace-deopt ./tests/index.js", + "trace-opt": "clear && node --trace-opt ./tests/index.js", + "gentesthtml": "0gentesthtml --script ./tests/index.js --include-dependencies @y/protocols > test.html" }, "exports": { ".": { "types": "./dist/src/index.d.ts", - "import": "./dist/yjs.mjs", - "require": "./dist/yjs.cjs" + "module": "./src/index.js", + "require": "./dist/yjs.cjs", + "import": "./src/index.js" + }, + "./internals": { + "types": "./dist/src/internals.d.ts", + "module": "./src/internals.js", + "require": "./dist/internals.cjs", + "import": "./src/internals.js" + }, + "./testHelper": { + "types": "./dist/tests/testHelper.d.ts", + "module": "./tests/testHelper.js", + "require": "./dist/testHelper.cjs", + "import": "./tests/testHelper.js" }, - "./src/index.js": "./src/index.js", - "./tests/testHelper.js": "./tests/testHelper.js", - "./testHelper": "./dist/testHelper.mjs", "./package.json": "./package.json" }, "files": [ - "dist/yjs.*", + "dist/*", "dist/src", "src", "tests/testHelper.js", - "dist/testHelper.mjs", - "sponsor-y.js" + "dist/testHelper.mjs" ], "dictionaries": { "test": "tests" @@ -49,8 +58,7 @@ "standard": { "ignore": [ "/dist", - "/node_modules", - "/docs" + "/node_modules" ] }, "repository": { @@ -74,19 +82,19 @@ }, "homepage": "https://docs.yjs.dev", "dependencies": { - "lib0": "^0.2.49" + "lib0": "^0.2.115-2" }, "devDependencies": { - "@rollup/plugin-commonjs": "^17.0.0", - "@rollup/plugin-node-resolve": "^11.2.1", - "concurrently": "^3.6.1", - "http-server": "^0.12.3", - "jsdoc": "^3.6.7", - "markdownlint-cli": "^0.23.2", - "rollup": "^2.60.0", - "standard": "^16.0.4", - "tui-jsdoc-template": "^1.2.2", - "typescript": "^4.4.4", - "y-protocols": "^1.0.5" + "@types/node": "^22.14.1", + "@y/protocols": "^1.0.6-1", + "concurrently": "^9.2.1", + "markdownlint-cli": "^0.45.0", + "rollup": "^4.52.5", + "standard": "^17.1.2", + "typescript": "^5.9.3" + }, + "engines": { + "npm": ">=8.0.0", + "node": ">=16.0.0" } } diff --git a/rollup.config.js b/rollup.config.js index 6bda75660..22d1c612f 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,111 +1,29 @@ -import nodeResolve from '@rollup/plugin-node-resolve' -import commonjs from '@rollup/plugin-commonjs' - -const localImports = process.env.LOCALIMPORTS - -const customModules = new Set([ - 'y-websocket', - 'y-codemirror', - 'y-ace', - 'y-textarea', - 'y-quill', - 'y-dom', - 'y-prosemirror' -]) -/** - * @type {Set} - */ -const customLibModules = new Set([ - 'lib0', - 'y-protocols' -]) -const debugResolve = { - resolveId (importee) { - if (importee === 'yjs') { - return `${process.cwd()}/src/index.js` - } - if (localImports) { - if (customModules.has(importee.split('/')[0])) { - return `${process.cwd()}/../${importee}/src/${importee}.js` - } - if (customLibModules.has(importee.split('/')[0])) { - return `${process.cwd()}/../${importee}` - } - } - return null - } -} - export default [{ - input: './src/index.js', - output: { - name: 'Y', - file: 'dist/yjs.cjs', - format: 'cjs', - sourcemap: true, - paths: path => { - if (/^lib0\//.test(path)) { - return `lib0/dist/${path.slice(5)}.cjs` - } - return path - } - }, - external: id => /^lib0\//.test(id) -}, { - input: './src/index.js', - output: { - name: 'Y', - file: 'dist/yjs.mjs', - format: 'esm', - sourcemap: true + // cjs output + input: { + yjs: './src/index.js', + testHelper: './tests/testHelper.js', + internals: './src/internals.js' }, - external: id => /^lib0\//.test(id) -}, { - input: './tests/testHelper.js', output: { - name: 'Y', - file: 'dist/testHelper.mjs', - format: 'esm', + dir: 'dist', + format: 'cjs', + entryFileNames: '[name].cjs', sourcemap: true }, - external: id => /^lib0\//.test(id) || id === 'yjs', - plugins: [{ - resolveId (importee) { - if (importee === '../src/index.js') { - return 'yjs' - } - return null - } - }] + external: id => /^(lib0|@y)\//.test(id) }, { - input: './tests/index.js', - output: { - name: 'test', - file: 'dist/tests.js', - format: 'iife', - sourcemap: true + // esm output + input: { + yjs: './src/index.js', + testHelper: './tests/testHelper.js', + internals: './src/internals.js' }, - plugins: [ - debugResolve, - nodeResolve({ - mainFields: ['module', 'browser', 'main'] - }), - commonjs() - ] -}, { - input: './tests/index.js', output: { - name: 'test', - file: 'dist/tests.cjs', - format: 'cjs', + dir: 'dist', + format: 'esm', + entryFileNames: '[name].mjs', sourcemap: true }, - plugins: [ - debugResolve, - nodeResolve({ - mainFields: ['module', 'main'] - }), - commonjs() - ], - external: ['isomorphic.js'] + external: id => /^(lib0|@y)\//.test(id) }] diff --git a/src/index.js b/src/index.js index 6624f8cb8..03ceba7c9 100644 --- a/src/index.js +++ b/src/index.js @@ -10,16 +10,14 @@ export { YXmlHook as XmlHook, YXmlElement as XmlElement, YXmlFragment as XmlFragment, - YXmlEvent, - YMapEvent, - YArrayEvent, - YTextEvent, YEvent, Item, AbstractStruct, GC, + Skip, ContentBinary, ContentDeleted, + ContentDoc, ContentEmbed, ContentFormat, ContentJSON, @@ -40,18 +38,18 @@ export { getState, Snapshot, createSnapshot, - createDeleteSet, - createDeleteSetFromStructStore, cleanupYTextFormatting, snapshot, emptySnapshot, findRootTypeKey, findIndexSS, getItem, + getItemCleanStart, + getItemCleanEnd, typeListToArraySnapshot, typeMapGetSnapshot, + typeMapGetAllSnapshot, createDocFromSnapshot, - iterateDeletedStructs, applyUpdate, applyUpdateV2, readUpdate, @@ -70,10 +68,8 @@ export { decodeUpdate, decodeUpdateV2, relativePositionToJSON, - isDeleted, isParentOf, equalSnapshots, - PermanentUserData, // @TODO experimental tryGc, transact, AbstractConnector, @@ -90,7 +86,35 @@ export { diffUpdateV2, convertUpdateFormatV1ToV2, convertUpdateFormatV2ToV1, - UpdateEncoderV1 + obfuscateUpdate, + obfuscateUpdateV2, + UpdateEncoderV1, + UpdateEncoderV2, + UpdateDecoderV1, + UpdateDecoderV2, + snapshotContainsUpdate, + // idset + IdSet, + equalIdSets, + createDeleteSetFromStructStore, + IdMap, + createIdMap, + createAttributionItem, + createInsertSetFromStructStore as createInsertionSetFromStructStore, + diffIdMap, + diffIdSet, + AttributionItem as Attribution, + encodeIdMap, + createIdMapFromIdSet, + TwosetAttributionManager, + noAttributionsManager, + AbstractAttributionManager, + iterateStructsByIdSet, + createAttributionManagerFromDiff, + DiffAttributionManager, + createIdSet, + mergeIdSets, + cloneDoc } from './internals.js' const glo = /** @type {any} */ (typeof globalThis !== 'undefined' diff --git a/src/internals.js b/src/internals.js index bc386f0a8..a67f6e83b 100644 --- a/src/internals.js +++ b/src/internals.js @@ -1,6 +1,5 @@ - export * from './utils/AbstractConnector.js' -export * from './utils/DeleteSet.js' +export * from './utils/IdSet.js' export * from './utils/Doc.js' export * from './utils/UpdateDecoder.js' export * from './utils/UpdateEncoder.js' @@ -9,7 +8,6 @@ export * from './utils/EventHandler.js' export * from './utils/ID.js' export * from './utils/isParentOf.js' export * from './utils/logging.js' -export * from './utils/PermanentUserData.js' export * from './utils/RelativePosition.js' export * from './utils/Snapshot.js' export * from './utils/StructStore.js' @@ -17,6 +15,9 @@ export * from './utils/Transaction.js' export * from './utils/UndoManager.js' export * from './utils/updates.js' export * from './utils/YEvent.js' +export * from './utils/StructSet.js' +export * from './utils/IdMap.js' +export * from './utils/AttributionManager.js' export * from './types/AbstractType.js' export * from './types/YArray.js' @@ -24,7 +25,6 @@ export * from './types/YMap.js' export * from './types/YText.js' export * from './types/YXmlFragment.js' export * from './types/YXmlElement.js' -export * from './types/YXmlEvent.js' export * from './types/YXmlHook.js' export * from './types/YXmlText.js' diff --git a/src/structs/AbstractStruct.js b/src/structs/AbstractStruct.js index 38457aef7..9324a1b47 100644 --- a/src/structs/AbstractStruct.js +++ b/src/structs/AbstractStruct.js @@ -1,4 +1,3 @@ - import { UpdateEncoderV1, UpdateEncoderV2, ID, Transaction // eslint-disable-line } from '../internals.js' @@ -27,7 +26,7 @@ export class AbstractStruct { * This method is already assuming that `this.id.clock + this.length === this.id.clock`. * Also this method does *not* remove right from StructStore! * @param {AbstractStruct} right - * @return {boolean} wether this merged with right + * @return {boolean} whether this merged with right */ mergeWith (right) { return false @@ -49,4 +48,12 @@ export class AbstractStruct { integrate (transaction, offset) { throw error.methodUnimplemented() } + + /** + * @param {number} diff + * @return {import('../internals.js').GC|import('../internals.js').Item} + */ + splice (diff) { + throw error.methodUnimplemented() + } } diff --git a/src/structs/ContentAny.js b/src/structs/ContentAny.js index 613144d82..4e32ac83b 100644 --- a/src/structs/ContentAny.js +++ b/src/structs/ContentAny.js @@ -2,6 +2,11 @@ import { UpdateEncoderV1, UpdateEncoderV2, UpdateDecoderV1, UpdateDecoderV2, Transaction, Item, StructStore // eslint-disable-line } from '../internals.js' +import * as env from 'lib0/environment' +import * as object from 'lib0/object' + +const isDevMode = env.getVariable('node_env') === 'development' + export class ContentAny { /** * @param {Array} arr @@ -11,6 +16,7 @@ export class ContentAny { * @type {Array} */ this.arr = arr + isDevMode && object.deepFreeze(arr) } /** @@ -70,17 +76,18 @@ export class ContentAny { */ delete (transaction) {} /** - * @param {StructStore} store + * @param {Transaction} _tr */ - gc (store) {} + gc (_tr) {} /** * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder * @param {number} offset + * @param {number} offsetEnd */ - write (encoder, offset) { - const len = this.arr.length - encoder.writeLen(len - offset) - for (let i = offset; i < len; i++) { + write (encoder, offset, offsetEnd) { + const end = this.arr.length - offsetEnd + encoder.writeLen(end - offset) + for (let i = offset; i < end; i++) { const c = this.arr[i] encoder.writeAny(c) } diff --git a/src/structs/ContentBinary.js b/src/structs/ContentBinary.js index 2708f53b7..580b60fe2 100644 --- a/src/structs/ContentBinary.js +++ b/src/structs/ContentBinary.js @@ -66,14 +66,15 @@ export class ContentBinary { */ delete (transaction) {} /** - * @param {StructStore} store + * @param {Transaction} _tr */ - gc (store) {} + gc (_tr) {} /** * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder - * @param {number} offset + * @param {number} _offset + * @param {number} _offsetEnd */ - write (encoder, offset) { + write (encoder, _offset, _offsetEnd) { encoder.writeBuf(this.content) } diff --git a/src/structs/ContentDeleted.js b/src/structs/ContentDeleted.js index 7225e1f61..57b5504b4 100644 --- a/src/structs/ContentDeleted.js +++ b/src/structs/ContentDeleted.js @@ -1,6 +1,5 @@ - import { - addToDeleteSet, + addToIdSet, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, StructStore, Item, Transaction // eslint-disable-line } from '../internals.js' @@ -64,24 +63,25 @@ export class ContentDeleted { * @param {Item} item */ integrate (transaction, item) { - addToDeleteSet(transaction.deleteSet, item.id.client, item.id.clock, this.len) + addToIdSet(transaction.deleteSet, item.id.client, item.id.clock, this.len) item.markDeleted() } /** - * @param {Transaction} transaction + * @param {Transaction} _transaction */ - delete (transaction) {} + delete (_transaction) {} /** - * @param {StructStore} store + * @param {Transaction} _tr */ - gc (store) {} + gc (_tr) {} /** * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder * @param {number} offset + * @param {number} offsetEnd */ - write (encoder, offset) { - encoder.writeLen(this.len - offset) + write (encoder, offset, offsetEnd) { + encoder.writeLen(this.len - offset - offsetEnd) } /** diff --git a/src/structs/ContentDoc.js b/src/structs/ContentDoc.js index 2c3bf8a69..6df705568 100644 --- a/src/structs/ContentDoc.js +++ b/src/structs/ContentDoc.js @@ -1,4 +1,3 @@ - import { Doc, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, StructStore, Transaction, Item // eslint-disable-line } from '../internals.js' @@ -111,15 +110,16 @@ export class ContentDoc { } /** - * @param {StructStore} store + * @param {Transaction} _tr */ - gc (store) { } + gc (_tr) {} /** * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder - * @param {number} offset + * @param {number} _offset + * @param {number} _offsetEnd */ - write (encoder, offset) { + write (encoder, _offset, _offsetEnd) { encoder.writeString(this.doc.guid) encoder.writeAny(this.opts) } diff --git a/src/structs/ContentEmbed.js b/src/structs/ContentEmbed.js index a64c53fa4..6d6ce53a5 100644 --- a/src/structs/ContentEmbed.js +++ b/src/structs/ContentEmbed.js @@ -1,4 +1,3 @@ - import { UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, StructStore, Item, Transaction // eslint-disable-line } from '../internals.js' @@ -70,14 +69,15 @@ export class ContentEmbed { */ delete (transaction) {} /** - * @param {StructStore} store + * @param {Transaction} _tr */ - gc (store) {} + gc (_tr) {} /** * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder - * @param {number} offset + * @param {number} _offset + * @param {number} _offsetEnd */ - write (encoder, offset) { + write (encoder, _offset, _offsetEnd) { encoder.writeJSON(this.embed) } diff --git a/src/structs/ContentFormat.js b/src/structs/ContentFormat.js index 6ac4c29c8..1046d2556 100644 --- a/src/structs/ContentFormat.js +++ b/src/structs/ContentFormat.js @@ -1,6 +1,5 @@ - import { - AbstractType, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Item, StructStore, Transaction // eslint-disable-line + YText, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Item, StructStore, Transaction // eslint-disable-line } from '../internals.js' import * as error from 'lib0/error' @@ -47,43 +46,46 @@ export class ContentFormat { } /** - * @param {number} offset + * @param {number} _offset * @return {ContentFormat} */ - splice (offset) { + splice (_offset) { throw error.methodUnimplemented() } /** - * @param {ContentFormat} right + * @param {ContentFormat} _right * @return {boolean} */ - mergeWith (right) { + mergeWith (_right) { return false } /** - * @param {Transaction} transaction + * @param {Transaction} _transaction * @param {Item} item */ - integrate (transaction, item) { + integrate (_transaction, item) { // @todo searchmarker are currently unsupported for rich text documents - /** @type {AbstractType} */ (item.parent)._searchMarker = null + const p = /** @type {YText} */ (item.parent) + p._searchMarker = null + p._hasFormatting = true } /** - * @param {Transaction} transaction + * @param {Transaction} _transaction */ - delete (transaction) {} + delete (_transaction) {} /** - * @param {StructStore} store + * @param {Transaction} _tr */ - gc (store) {} + gc (_tr) {} /** * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder - * @param {number} offset + * @param {number} _offset + * @param {number} _offsetEnd */ - write (encoder, offset) { + write (encoder, _offset, _offsetEnd) { encoder.writeKey(this.key) encoder.writeJSON(this.value) } diff --git a/src/structs/ContentJSON.js b/src/structs/ContentJSON.js index 29b2f7540..f20d7306f 100644 --- a/src/structs/ContentJSON.js +++ b/src/structs/ContentJSON.js @@ -73,17 +73,18 @@ export class ContentJSON { */ delete (transaction) {} /** - * @param {StructStore} store + * @param {Transaction} _tr */ - gc (store) {} + gc (_tr) {} /** * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder * @param {number} offset + * @param {number} offsetEnd */ - write (encoder, offset) { - const len = this.arr.length - encoder.writeLen(len - offset) - for (let i = offset; i < len; i++) { + write (encoder, offset, offsetEnd) { + const end = this.arr.length - offsetEnd + encoder.writeLen(end - offset) + for (let i = offset; i < end; i++) { const c = this.arr[i] encoder.writeString(c === undefined ? 'undefined' : JSON.stringify(c)) } diff --git a/src/structs/ContentString.js b/src/structs/ContentString.js index fa023ef95..6f1064b5a 100644 --- a/src/structs/ContentString.js +++ b/src/structs/ContentString.js @@ -84,15 +84,16 @@ export class ContentString { */ delete (transaction) {} /** - * @param {StructStore} store + * @param {Transaction} _tr */ - gc (store) {} + gc (_tr) {} /** * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder * @param {number} offset + * @param {number} offsetEnd */ - write (encoder, offset) { - encoder.writeString(offset === 0 ? this.str : this.str.slice(offset)) + write (encoder, offset, offsetEnd) { + encoder.writeString((offset === 0 && offsetEnd === 0) ? this.str : this.str.slice(offset, this.str.length - offsetEnd)) } /** diff --git a/src/structs/ContentType.js b/src/structs/ContentType.js index 96a731de5..25ff3f67a 100644 --- a/src/structs/ContentType.js +++ b/src/structs/ContentType.js @@ -1,4 +1,3 @@ - import { readYArray, readYMap, @@ -7,13 +6,17 @@ import { readYXmlFragment, readYXmlHook, readYXmlText, - UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, StructStore, Transaction, Item, YEvent, AbstractType // eslint-disable-line + UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Transaction, Item // eslint-disable-line } from '../internals.js' +/** + * @typedef {import('../utils/types.js').YType} YType_CT + */ + import * as error from 'lib0/error' /** - * @type {Array>} + * @type {Array<(decoder: UpdateDecoderV1 | UpdateDecoderV2)=>(import('../utils/types.js').YType)>} * @private */ export const typeRefs = [ @@ -39,11 +42,11 @@ export const YXmlTextRefID = 6 */ export class ContentType { /** - * @param {AbstractType} type + * @param {YType_CT} type */ constructor (type) { /** - * @type {AbstractType} + * @type {YType_CT} */ this.type = type } @@ -77,18 +80,18 @@ export class ContentType { } /** - * @param {number} offset + * @param {number} _offset * @return {ContentType} */ - splice (offset) { + splice (_offset) { throw error.methodUnimplemented() } /** - * @param {ContentType} right + * @param {ContentType} _right * @return {boolean} */ - mergeWith (right) { + mergeWith (_right) { return false } @@ -108,7 +111,7 @@ export class ContentType { while (item !== null) { if (!item.deleted) { item.delete(transaction) - } else { + } else if (!transaction.insertSet.hasId(item.id)) { // This will be gc'd later and we want to merge it if possible // We try to merge all deleted items after each transaction, // but we have no knowledge about that this needs to be merged @@ -120,7 +123,7 @@ export class ContentType { this.type._map.forEach(item => { if (!item.deleted) { item.delete(transaction) - } else { + } else if (!transaction.insertSet.hasId(item.id)) { // same as above transaction._mergeStructs.push(item) } @@ -129,18 +132,18 @@ export class ContentType { } /** - * @param {StructStore} store + * @param {Transaction} tr */ - gc (store) { + gc (tr) { let item = this.type._start while (item !== null) { - item.gc(store, true) + item.gc(tr, true) item = item.right } this.type._start = null this.type._map.forEach(/** @param {Item | null} item */ (item) => { while (item !== null) { - item.gc(store, true) + item.gc(tr, true) item = item.left } }) @@ -149,9 +152,10 @@ export class ContentType { /** * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder - * @param {number} offset + * @param {number} _offset + * @param {number} _offsetEnd */ - write (encoder, offset) { + write (encoder, _offset, _offsetEnd) { this.type._write(encoder) } diff --git a/src/structs/GC.js b/src/structs/GC.js index 42d71f737..7ee1bc338 100644 --- a/src/structs/GC.js +++ b/src/structs/GC.js @@ -1,8 +1,10 @@ - import { AbstractStruct, addStruct, - UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, StructStore, Transaction, ID // eslint-disable-line + addStructToIdSet, + addToIdSet, + createID, + UpdateEncoderV1, UpdateEncoderV2, StructStore, Transaction // eslint-disable-line } from '../internals.js' export const structGCRefNumber = 0 @@ -31,31 +33,48 @@ export class GC extends AbstractStruct { /** * @param {Transaction} transaction - * @param {number} offset + * @param {number} offset - @todo remove offset parameter */ integrate (transaction, offset) { if (offset > 0) { this.id.clock += offset this.length -= offset } + addToIdSet(transaction.deleteSet, this.id.client, this.id.clock, this.length) + addStructToIdSet(transaction.insertSet, this) addStruct(transaction.doc.store, this) } /** * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder * @param {number} offset + * @param {number} offsetEnd */ - write (encoder, offset) { + write (encoder, offset, offsetEnd) { encoder.writeInfo(structGCRefNumber) - encoder.writeLen(this.length - offset) + encoder.writeLen(this.length - offset - offsetEnd) } /** - * @param {Transaction} transaction - * @param {StructStore} store + * @param {Transaction} _transaction + * @param {StructStore} _store * @return {null | number} */ - getMissing (transaction, store) { + getMissing (_transaction, _store) { return null } + + /** + * gc structs can't be spliced. + * + * If this feature is required in the future, then need to try to merge this struct after + * transaction. + * + * @param {number} diff + */ + splice (diff) { + const other = new GC(createID(this.id.client, this.id.clock + diff), this.length - diff) + this.length = diff + return other + } } diff --git a/src/structs/Item.js b/src/structs/Item.js index 704dcd32e..137d328dc 100644 --- a/src/structs/Item.js +++ b/src/structs/Item.js @@ -1,11 +1,10 @@ - import { GC, getState, AbstractStruct, replaceStruct, addStruct, - addToDeleteSet, + addToIdSet, findRootTypeKey, compareIDs, getItem, @@ -22,12 +21,17 @@ import { readContentFormat, readContentType, addChangedTypeToTransaction, - isDeleted, - DeleteSet, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, ContentType, ContentDeleted, StructStore, ID, AbstractType, Transaction // eslint-disable-line + addStructToIdSet, + IdSet, StackItem, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, ContentType, ContentDeleted, StructStore, ID, AbstractType, Transaction, // eslint-disable-line } from '../internals.js' import * as error from 'lib0/error' import * as binary from 'lib0/binary' +import * as array from 'lib0/array' + +/** + * @typedef {import('../utils/types.js').YType} YType__ + */ /** * @todo This should return several items @@ -68,13 +72,13 @@ export const followRedone = (store, id) => { export const keepItem = (item, keep) => { while (item !== null && item.keep !== keep) { item.keep = keep - item = /** @type {AbstractType} */ (item.parent)._item + item = /** @type {YType__} */ (item.parent)._item } } /** * Split leftItem into two items - * @param {Transaction} transaction + * @param {Transaction?} transaction * @param {Item} leftItem * @param {number} diff * @return {Item} @@ -104,36 +108,68 @@ export const splitItem = (transaction, leftItem, diff) => { if (leftItem.redone !== null) { rightItem.redone = createID(leftItem.redone.client, leftItem.redone.clock + diff) } - // update left (do not set leftItem.rightOrigin as it will lead to problems when syncing) - leftItem.right = rightItem - // update right - if (rightItem.right !== null) { - rightItem.right.left = rightItem - } - // right is more specific. - transaction._mergeStructs.push(rightItem) - // update parent._map - if (rightItem.parentSub !== null && rightItem.right === null) { - /** @type {AbstractType} */ (rightItem.parent)._map.set(rightItem.parentSub, rightItem) + if (transaction != null) { + // update left (do not set leftItem.rightOrigin as it will lead to problems when syncing) + leftItem.right = rightItem + // update right + if (rightItem.right !== null) { + rightItem.right.left = rightItem + } + // right is more specific. + transaction._mergeStructs.push(rightItem) + // update parent._map + if (rightItem.parentSub !== null && rightItem.right === null) { + /** @type {YType__} */ (rightItem.parent)._map.set(rightItem.parentSub, rightItem) + } + } else { + rightItem.left = null + rightItem.right = null } leftItem.length = diff return rightItem } +/** + * More generalized version of splitItem. Split leftStruct into two structs + * @param {Transaction?} transaction + * @param {AbstractStruct} leftStruct + * @param {number} diff + * @return {GC|Item} + * + * @function + * @private + */ +export const splitStruct = (transaction, leftStruct, diff) => { + if (leftStruct instanceof Item) { + return splitItem(transaction, leftStruct, diff) + } else { + const rightItem = leftStruct.splice(diff) + transaction?._mergeStructs.push(rightItem) + return rightItem + } +} + +/** + * @param {Array} stack + * @param {ID} id + */ +const isDeletedByUndoStack = (stack, id) => array.some(stack, /** @param {StackItem} s */ s => s.deletions.hasId(id)) + /** * Redoes the effect of this operation. * * @param {Transaction} transaction The Yjs instance. * @param {Item} item * @param {Set} redoitems - * @param {DeleteSet} itemsToDelete + * @param {IdSet} itemsToDelete * @param {boolean} ignoreRemoteMapChanges + * @param {import('../utils/UndoManager.js').UndoManager} um * * @return {Item|null} * * @private */ -export const redoItem = (transaction, item, redoitems, itemsToDelete, ignoreRemoteMapChanges) => { +export const redoItem = (transaction, item, redoitems, itemsToDelete, ignoreRemoteMapChanges, um) => { const doc = transaction.doc const store = doc.store const ownClientID = doc.clientID @@ -141,7 +177,7 @@ export const redoItem = (transaction, item, redoitems, itemsToDelete, ignoreRemo if (redone !== null) { return getItemCleanStart(transaction, redone) } - let parentItem = /** @type {AbstractType} */ (item.parent)._item + let parentItem = /** @type {YType__} */ (item.parent)._item /** * @type {Item|null} */ @@ -153,14 +189,17 @@ export const redoItem = (transaction, item, redoitems, itemsToDelete, ignoreRemo // make sure that parent is redone if (parentItem !== null && parentItem.deleted === true) { // try to undo parent if it will be undone anyway - if (parentItem.redone === null && (!redoitems.has(parentItem) || redoItem(transaction, parentItem, redoitems, itemsToDelete, ignoreRemoteMapChanges) === null)) { + if (parentItem.redone === null && (!redoitems.has(parentItem) || redoItem(transaction, parentItem, redoitems, itemsToDelete, ignoreRemoteMapChanges, um) === null)) { return null } while (parentItem.redone !== null) { parentItem = getItemCleanStart(transaction, parentItem.redone) } } - const parentType = parentItem === null ? /** @type {AbstractType} */ (item.parent) : /** @type {ContentType} */ (parentItem.content).type + /** + * @type {YType__} + */ + const parentType = /** @type {YType__} */ (parentItem === null ? item.parent : /** @type {ContentType} */ (parentItem.content).type) if (item.parentSub === null) { // Is an array item. Insert at the old position @@ -173,10 +212,10 @@ export const redoItem = (transaction, item, redoitems, itemsToDelete, ignoreRemo */ let leftTrace = left // trace redone until parent matches - while (leftTrace !== null && /** @type {AbstractType} */ (leftTrace.parent)._item !== parentItem) { + while (leftTrace !== null && /** @type {YType__} */ (leftTrace.parent)._item !== parentItem) { leftTrace = leftTrace.redone === null ? null : getItemCleanStart(transaction, leftTrace.redone) } - if (leftTrace !== null && /** @type {AbstractType} */ (leftTrace.parent)._item === parentItem) { + if (leftTrace !== null && /** @type {YType__} */ (leftTrace.parent)._item === parentItem) { left = leftTrace break } @@ -188,10 +227,10 @@ export const redoItem = (transaction, item, redoitems, itemsToDelete, ignoreRemo */ let rightTrace = right // trace redone until parent matches - while (rightTrace !== null && /** @type {AbstractType} */ (rightTrace.parent)._item !== parentItem) { + while (rightTrace !== null && /** @type {YType__} */ (rightTrace.parent)._item !== parentItem) { rightTrace = rightTrace.redone === null ? null : getItemCleanStart(transaction, rightTrace.redone) } - if (rightTrace !== null && /** @type {AbstractType} */ (rightTrace.parent)._item === parentItem) { + if (rightTrace !== null && /** @type {YType__} */ (rightTrace.parent)._item === parentItem) { right = rightTrace break } @@ -203,13 +242,10 @@ export const redoItem = (transaction, item, redoitems, itemsToDelete, ignoreRemo left = item // Iterate right while right is in itemsToDelete // If it is intended to delete right while item is redone, we can expect that item should replace right. - while (left !== null && left.right !== null && isDeleted(itemsToDelete, left.right.id)) { + while (left !== null && left.right !== null && (left.right.redone || itemsToDelete.hasId(left.right.id) || isDeletedByUndoStack(um.undoStack, left.right.id) || isDeletedByUndoStack(um.redoStack, left.right.id))) { left = left.right - } - // follow redone - // trace redone until parent matches - while (left !== null && left.redone !== null) { - left = getItemCleanStart(transaction, left.redone) + // follow redone + while (left.redone) left = getItemCleanStart(transaction, left.redone) } if (left && left.right !== null) { // It is not possible to redo this item because it conflicts with a @@ -246,7 +282,7 @@ export class Item extends AbstractStruct { * @param {ID | null} origin * @param {Item | null} right * @param {ID | null} rightOrigin - * @param {AbstractType|ID|null} parent Is a type if integrated, is null if it is possible to copy parent from left or right, is ID before integration to search for it. + * @param {AbstractType|ID|null} parent Is a type if integrated, is null if it is possible to copy parent from left or right, is ID before integration to search for it. * @param {string | null} parentSub * @param {AbstractContent} content */ @@ -273,7 +309,7 @@ export class Item extends AbstractStruct { */ this.rightOrigin = rightOrigin /** - * @type {AbstractType|ID|null} + * @type {AbstractType|ID|null} */ this.parent = parent /** @@ -362,18 +398,16 @@ export class Item extends AbstractStruct { * @return {null | number} */ getMissing (transaction, store) { - if (this.origin && this.origin.client !== this.id.client && this.origin.clock >= getState(store, this.origin.client)) { + if (this.origin && (this.origin.clock >= getState(store, this.origin.client) || store.skips.hasId(this.origin))) { return this.origin.client } - if (this.rightOrigin && this.rightOrigin.client !== this.id.client && this.rightOrigin.clock >= getState(store, this.rightOrigin.client)) { + if (this.rightOrigin && (this.rightOrigin.clock >= getState(store, this.rightOrigin.client) || store.skips.hasId(this.rightOrigin))) { return this.rightOrigin.client } - if (this.parent && this.parent.constructor === ID && this.id.client !== this.parent.client && this.parent.clock >= getState(store, this.parent.client)) { + if (this.parent && this.parent.constructor === ID && (this.parent.clock >= getState(store, this.parent.client) || store.skips.hasId(this.parent))) { return this.parent.client } - // We have all missing ids, now find the items - if (this.origin) { this.left = getItemCleanEnd(transaction, store, this.origin) this.origin = this.left.lastId @@ -384,14 +418,12 @@ export class Item extends AbstractStruct { } if ((this.left && this.left.constructor === GC) || (this.right && this.right.constructor === GC)) { this.parent = null - } - // only set parent if this shouldn't be garbage collected - if (!this.parent) { + } else if (!this.parent) { + // only set parent if this shouldn't be garbage collected if (this.left && this.left.constructor === Item) { this.parent = this.left.parent this.parentSub = this.left.parentSub - } - if (this.right && this.right.constructor === Item) { + } else if (this.right && this.right.constructor === Item) { this.parent = this.right.parent this.parentSub = this.right.parentSub } @@ -512,10 +544,11 @@ export class Item extends AbstractStruct { if (this.parentSub === null && this.countable && !this.deleted) { /** @type {AbstractType} */ (this.parent)._length += this.length } + addStructToIdSet(transaction.insertSet, this) addStruct(transaction.doc.store, this) this.content.integrate(transaction, this) // add parent to transaction.changed - addChangedTypeToTransaction(transaction, /** @type {AbstractType} */ (this.parent), this.parentSub) + addChangedTypeToTransaction(transaction, /** @type {import('../utils/types.js').YType} */ (this.parent), this.parentSub) if ((/** @type {AbstractType} */ (this.parent)._item !== null && /** @type {AbstractType} */ (this.parent)._item.deleted) || (this.parentSub !== null && this.right !== null)) { // delete if parent is deleted or if this is not the current attribute value of parent this.delete(transaction) @@ -609,29 +642,29 @@ export class Item extends AbstractStruct { */ delete (transaction) { if (!this.deleted) { - const parent = /** @type {AbstractType} */ (this.parent) + const parent = /** @type {import('../utils/types.js').YType} */ (this.parent) // adjust the length of parent if (this.countable && this.parentSub === null) { parent._length -= this.length } this.markDeleted() - addToDeleteSet(transaction.deleteSet, this.id.client, this.id.clock, this.length) + addToIdSet(transaction.deleteSet, this.id.client, this.id.clock, this.length) addChangedTypeToTransaction(transaction, parent, this.parentSub) this.content.delete(transaction) } } /** - * @param {StructStore} store + * @param {Transaction} tr * @param {boolean} parentGCd */ - gc (store, parentGCd) { + gc (tr, parentGCd) { if (!this.deleted) { throw error.unexpectedCase() } - this.content.gc(store) + this.content.gc(tr) if (parentGCd) { - replaceStruct(store, this, new GC(this.id, this.length)) + replaceStruct(tr, this, new GC(this.id, this.length)) } else { this.content = new ContentDeleted(this.length) } @@ -645,8 +678,9 @@ export class Item extends AbstractStruct { * * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder The encoder to write data to. * @param {number} offset + * @param {number} offsetEnd */ - write (encoder, offset) { + write (encoder, offset, offsetEnd) { const origin = offset > 0 ? createID(this.id.client, this.id.clock + offset - 1) : this.origin const rightOrigin = this.rightOrigin const parentSub = this.parentSub @@ -688,7 +722,7 @@ export class Item extends AbstractStruct { encoder.writeString(parentSub) } } - this.content.write(encoder, offset) + this.content.write(encoder, offset, offsetEnd) } } @@ -756,48 +790,49 @@ export class AbstractContent { } /** - * @param {number} offset + * @param {number} _offset * @return {AbstractContent} */ - splice (offset) { + splice (_offset) { throw error.methodUnimplemented() } /** - * @param {AbstractContent} right + * @param {AbstractContent} _right * @return {boolean} */ - mergeWith (right) { + mergeWith (_right) { throw error.methodUnimplemented() } /** - * @param {Transaction} transaction - * @param {Item} item + * @param {Transaction} _transaction + * @param {Item} _item */ - integrate (transaction, item) { + integrate (_transaction, _item) { throw error.methodUnimplemented() } /** - * @param {Transaction} transaction + * @param {Transaction} _transaction */ - delete (transaction) { + delete (_transaction) { throw error.methodUnimplemented() } /** - * @param {StructStore} store + * @param {Transaction} _transaction */ - gc (store) { + gc (_transaction) { throw error.methodUnimplemented() } /** - * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder - * @param {number} offset + * @param {UpdateEncoderV1 | UpdateEncoderV2} _encoder + * @param {number} _offset + * @param {number} _offsetEnd */ - write (encoder, offset) { + write (_encoder, _offset, _offsetEnd) { throw error.methodUnimplemented() } diff --git a/src/structs/Skip.js b/src/structs/Skip.js index 3db2399d6..1fa81a58c 100644 --- a/src/structs/Skip.js +++ b/src/structs/Skip.js @@ -1,9 +1,11 @@ - import { AbstractStruct, - UpdateEncoderV1, UpdateEncoderV2, StructStore, Transaction, ID // eslint-disable-line + addStruct, + addToIdSet, + UpdateEncoderV1, UpdateEncoderV2, StructStore, Transaction, // eslint-disable-line + createID } from '../internals.js' -import * as error from 'lib0/error' + import * as encoding from 'lib0/encoding' export const structSkipRefNumber = 10 @@ -13,7 +15,7 @@ export const structSkipRefNumber = 10 */ export class Skip extends AbstractStruct { get deleted () { - return true + return false } delete () {} @@ -35,8 +37,12 @@ export class Skip extends AbstractStruct { * @param {number} offset */ integrate (transaction, offset) { - // skip structs cannot be integrated - error.unexpectedCase() + if (offset > 0) { + this.id.clock += offset + this.length -= offset + } + addToIdSet(transaction.doc.store.skips, this.id.client, this.id.clock, this.length) + addStruct(transaction.doc.store, this) } /** @@ -50,11 +56,20 @@ export class Skip extends AbstractStruct { } /** - * @param {Transaction} transaction - * @param {StructStore} store + * @param {Transaction} _transaction + * @param {StructStore} _store * @return {null | number} */ - getMissing (transaction, store) { + getMissing (_transaction, _store) { return null } + + /** + * @param {number} diff + */ + splice (diff) { + const other = new Skip(createID(this.id.client, this.id.clock + diff), this.length - diff) + this.length = diff + return other + } } diff --git a/src/types/AbstractType.js b/src/types/AbstractType.js index 617ea19b2..1f4f04dd5 100644 --- a/src/types/AbstractType.js +++ b/src/types/AbstractType.js @@ -1,4 +1,3 @@ - import { removeEventHandlerListener, callEventHandlerListeners, @@ -9,15 +8,41 @@ import { ContentType, createID, ContentAny, + ContentFormat, ContentBinary, + ContentJSON, + ContentDeleted, + ContentString, + ContentEmbed, getItemCleanStart, - ContentDoc, YText, YArray, UpdateEncoderV1, UpdateEncoderV2, Doc, Snapshot, Transaction, EventHandler, YEvent, Item, // eslint-disable-line + noAttributionsManager, + transact, + ItemTextListPosition, + insertText, + deleteText, + ContentDoc, UpdateEncoderV1, UpdateEncoderV2, Doc, Snapshot, Transaction, EventHandler, YEvent, Item, createAttributionFromAttributionItems, AbstractAttributionManager, YXmlElement, // eslint-disable-line } from '../internals.js' +import * as delta from 'lib0/delta' +import * as array from 'lib0/array' import * as map from 'lib0/map' import * as iterator from 'lib0/iterator' import * as error from 'lib0/error' import * as math from 'lib0/math' +import * as log from 'lib0/logging' +import * as object from 'lib0/object' + +/** + * @typedef {import('../utils/types.js').YType} YType_ + */ +/** + * @typedef {import('../utils/types.js').YValue} _YValue + */ + +/** + * https://docs.yjs.dev/getting-started/working-with-shared-types#caveats + */ +export const warnPrematureAccess = () => { log.warn('Invalid access: Add Yjs type to a document before reading data.') } const maxSearchMarker = 80 @@ -91,7 +116,7 @@ const markPosition = (searchMarker, p, index) => { * * This function always returns a refreshed marker (updated timestamp) * - * @param {AbstractType} yarray + * @param {import('../utils/types.js').YType} yarray * @param {number} index */ export const findMarker = (yarray, index) => { @@ -150,14 +175,14 @@ export const findMarker = (yarray, index) => { // } // } // if (marker) { - // if (window.lengthes == null) { - // window.lengthes = [] - // window.getLengthes = () => window.lengthes.sort((a, b) => a - b) + // if (window.lengths == null) { + // window.lengths = [] + // window.getLengths = () => window.lengths.sort((a, b) => a - b) // } - // window.lengthes.push(marker.index - pindex) + // window.lengths.push(marker.index - pindex) // console.log('distance', marker.index - pindex, 'len', p && p.parent.length) // } - if (marker !== null && math.abs(marker.index - pindex) < /** @type {YText|YArray} */ (p.parent).length / maxSearchMarker) { + if (marker !== null && math.abs(marker.index - pindex) < /** @type {any} */ (p.parent).length / maxSearchMarker) { // adjust existing marker overwriteMarker(marker, p, pindex) return marker @@ -212,10 +237,11 @@ export const updateMarkerChanges = (searchMarker, index, len) => { /** * Accumulate all (list) children of a type and return them as an Array. * - * @param {AbstractType} t + * @param {import('../utils/types.js').YType} t * @return {Array} */ export const getTypeChildren = t => { + t.doc ?? warnPrematureAccess() let s = t._start const arr = [] while (s) { @@ -229,10 +255,9 @@ export const getTypeChildren = t => { * Call event listeners with an event. This will also add an event to all * parents (for `.observeDeep` handlers). * - * @template EventType - * @param {AbstractType} type + * @param {import('../utils/types.js').YType} type * @param {Transaction} transaction - * @param {EventType} event + * @param {YEvent} event */ export const callTypeObservers = (type, transaction, event) => { const changedType = type @@ -243,14 +268,15 @@ export const callTypeObservers = (type, transaction, event) => { if (type._item === null) { break } - type = /** @type {AbstractType} */ (type._item.parent) + type = /** @type {import('../utils/types.js').YType} */ (type._item.parent) } - callEventHandlerListeners(changedType._eH, event, transaction) + callEventHandlerListeners(/** @type {any} */ (changedType._eH), event, transaction) } /** - * @template EventType * Abstract Yjs Type class + * @template {delta.Delta} [EventDelta=any] + * @template {AbstractType} [Self=any] */ export class AbstractType { constructor () { @@ -273,7 +299,7 @@ export class AbstractType { this._length = 0 /** * Event handlers - * @type {EventHandler} + * @type {EventHandler,Transaction>} */ this._eH = createEventHandler() /** @@ -285,13 +311,25 @@ export class AbstractType { * @type {null | Array} */ this._searchMarker = null + /** + * @type {EventDelta?} + */ + this._prelim = null } /** - * @return {AbstractType|null} + * Returns a fresh delta that can be used to change this YType. + * @type {EventDelta} + */ + get change () { + return /** @type {any} */ (delta.create()) + } + + /** + * @return {import('../utils/types.js').YType|null} */ get parent () { - return this._item ? /** @type {AbstractType} */ (this._item.parent) : null + return /** @type {import('../utils/types.js').YType} */ (this._item ? this._item.parent : null) } /** @@ -307,26 +345,36 @@ export class AbstractType { _integrate (y, item) { this.doc = y this._item = item + if (this._prelim) { + this.applyDelta(this._prelim) + this._prelim = null + } } /** - * @return {AbstractType} + * @return {Self} */ _copy () { - throw error.methodUnimplemented() + // @ts-ignore + return new this.constructor() } /** - * @return {AbstractType} + * Makes a copy of this data type that can be included somewhere else. + * + * Note that the content is only readable _after_ it has been included somewhere in the Ydoc. + * + * @return {Self} */ clone () { + // @todo remove this method from othern types by doing `_copy().apply(this.getContent())` throw error.methodUnimplemented() } /** - * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder + * @param {UpdateEncoderV1 | UpdateEncoderV2} _encoder */ - _write (encoder) { } + _write (_encoder) { } /** * The first non-deleted item @@ -347,6 +395,8 @@ export class AbstractType { * @param {Set} parentSubs Keys changed on this type. `null` if list was modified. */ _callObserver (transaction, parentSubs) { + const event = new YEvent(/** @type {any} */ (this), transaction, parentSubs) + callTypeObservers(/** @type {any} */ (this), transaction, event) if (!transaction.local && this._searchMarker) { this._searchMarker.length = 0 } @@ -355,25 +405,31 @@ export class AbstractType { /** * Observe all events that are created on this type. * - * @param {function(EventType, Transaction):void} f Observer function + * @template {(target: YEvent, tr: Transaction) => void} F + * @param {F} f Observer function + * @return {F} */ observe (f) { addEventHandlerListener(this._eH, f) + return f } /** * Observe all events that are created by this type and its children. * - * @param {function(Array>,Transaction):void} f Observer function + * @template {function(Array>,Transaction):void} F + * @param {F} f Observer function + * @return {F} */ observeDeep (f) { addEventHandlerListener(this._dEH, f) + return f } /** * Unregister an observer function. * - * @param {function(EventType,Transaction):void} f Observer function + * @param {(type:YEvent,tr:Transaction)=>void} f Observer function */ unobserve (f) { removeEventHandlerListener(this._eH, f) @@ -393,8 +449,341 @@ export class AbstractType { * @return {any} */ toJSON () {} + + /** + * Render the difference to another ydoc (which can be empty) and highlight the differences with + * attributions. + * + * Note that deleted content that was not deleted in prevYdoc is rendered as an insertion with the + * attribution `{ isDeleted: true, .. }`. + * + * @template {boolean} [Deep=false] + * + * @param {AbstractAttributionManager} am + * @param {Object} [opts] + * @param {import('../utils/IdSet.js').IdSet?} [opts.itemsToRender] + * @param {boolean} [opts.retainInserts] - if true, retain rendered inserts with attributions + * @param {boolean} [opts.retainDeletes] - if true, retain rendered+attributed deletes only + * @param {Set?} [opts.renderAttrs] - set of attrs to render. if null, render all attributes + * @param {boolean} [opts.renderChildren] - if true, retain rendered+attributed deletes only + * @param {import('../utils/IdSet.js').IdSet?} [opts.deletedItems] - used for computing prevItem in attributes + * @param {Set|Map|null} [opts.modified] - set of types that should be rendered as modified children + * @param {Deep} [opts.deep] - render child types as delta + * @return {Deep extends true ? ToDeepEventDelta : EventDelta} The Delta representation of this type. + * + * @public + */ + getContent (am = noAttributionsManager, opts = {}) { + const { itemsToRender = null, retainInserts = false, retainDeletes = false, renderAttrs = null, renderChildren = true, deletedItems = null, modified = null, deep = false } = opts + /** + * @type {EventDelta extends delta.Delta ? delta.DeltaBuilder : never} + */ + const d = /** @type {any} */ (delta.create(/** @type {any} */ (this).nodeName || null)) + typeMapGetDelta(d, /** @type {any} */ (this), renderAttrs, am, deep, modified, deletedItems, itemsToRender) + if (renderChildren) { + /** + * @type {delta.FormattingAttributes} + */ + let currentAttributes = {} // saves all current attributes for insert + let usingCurrentAttributes = false + /** + * @type {delta.FormattingAttributes} + */ + let changedAttributes = {} // saves changed attributes for retain + let usingChangedAttributes = false + /** + * Logic for formatting attribute attribution + * Everything that comes after an formatting attribute is formatted by the user that created it. + * Two exceptions: + * - the user resets formatting to the previously known formatting that is not attributed + * - the user deletes a formatting attribute and hence restores the previously known formatting + * that is not attributed. + * @type {delta.FormattingAttributes} + */ + const previousUnattributedAttributes = {} // contains previously known unattributed formatting + /** + * @type {delta.FormattingAttributes} + */ + const previousAttributes = {} // The value before changes + /** + * @type {Array>} + */ + const cs = [] + for (let item = this._start; item !== null; cs.length = 0) { + if (itemsToRender != null) { + for (; item !== null && cs.length < 50; item = item.right) { + const rslice = itemsToRender.slice(item.id.client, item.id.clock, item.length) + let itemContent = rslice.length > 1 ? item.content.copy() : item.content + for (let ir = 0; ir < rslice.length; ir++) { + const idrange = rslice[ir] + const content = itemContent + if (ir !== rslice.length - 1) { + itemContent = itemContent.splice(idrange.len) + } + am.readContent(cs, item.id.client, idrange.clock, item.deleted, content, idrange.exists ? 2 : 0) + } + } + } else { + for (; item !== null && cs.length < 50; item = item.right) { + am.readContent(cs, item.id.client, item.id.clock, item.deleted, item.content, 1) + } + } + for (let i = 0; i < cs.length; i++) { + const c = cs[i] + // render (attributed) content even if it was deleted + const renderContent = c.render && (!c.deleted || c.attrs != null) + // content that was just deleted. It is not rendered as an insertion, because it doesn't + // have any attributes. + const renderDelete = c.render && c.deleted + // existing content that should be retained, only adding changed attributes + const retainContent = !c.render && (!c.deleted || c.attrs != null) + const attribution = (renderContent || c.content.constructor === ContentFormat) ? createAttributionFromAttributionItems(c.attrs, c.deleted) : null + switch (c.content.constructor) { + case ContentDeleted: { + if (renderDelete) d.delete(c.content.getLength()) + break + } + case ContentString: + if (renderContent) { + d.usedAttributes = currentAttributes + usingCurrentAttributes = true + if (c.deleted ? retainDeletes : retainInserts) { + d.retain(/** @type {ContentString} */ (c.content).str.length, null, attribution ?? {}) + } else { + d.insert(/** @type {ContentString} */ (c.content).str, null, attribution) + } + } else if (renderDelete) { + d.delete(c.content.getLength()) + } else if (retainContent) { + d.usedAttributes = changedAttributes + usingChangedAttributes = true + d.retain(c.content.getLength()) + } + break + case ContentEmbed: + case ContentAny: + case ContentJSON: + case ContentType: + case ContentBinary: + if (renderContent) { + d.usedAttributes = currentAttributes + usingCurrentAttributes = true + if (c.deleted ? retainDeletes : retainInserts) { + d.retain(c.content.getLength(), null, attribution ?? {}) + } else if (deep && c.content.constructor === ContentType) { + d.insert([/** @type {any} */(c.content).type.getContent(am, { ...opts, renderChildren: true, renderAttrs: null })], null, attribution) + } else { + d.insert(c.content.getContent(), null, attribution) + } + } else if (renderDelete) { + d.delete(1) + } else if (retainContent) { + if (c.content.constructor === ContentType && modified?.has(/** @type {ContentType} */ (c.content).type)) { + // @todo use current transaction instead + d.modify(/** @type {any} */ (c.content).type.getContent(am, opts)) + } else { + d.usedAttributes = changedAttributes + usingChangedAttributes = true + d.retain(1) + } + } + break + case ContentFormat: { + const { key, value } = /** @type {ContentFormat} */ (c.content) + const currAttrVal = currentAttributes[key] ?? null + if (attribution != null && (c.deleted || !object.hasProperty(previousUnattributedAttributes, key))) { + previousUnattributedAttributes[key] = c.deleted ? value : currAttrVal + } + // @todo write a function "updateCurrentAttributes" and "updateChangedAttributes" + // # Update Attributes + if (renderContent || renderDelete) { + // create fresh references + if (usingCurrentAttributes) { + currentAttributes = object.assign({}, currentAttributes) + usingCurrentAttributes = false + } + if (usingChangedAttributes) { + usingChangedAttributes = false + changedAttributes = object.assign({}, changedAttributes) + } + } + if (renderContent || renderDelete) { + if (c.deleted) { + // content was deleted, but is possibly attributed + if (!equalAttrs(value, currAttrVal)) { // do nothing if nothing changed + if (equalAttrs(currAttrVal, previousAttributes[key] ?? null) && changedAttributes[key] !== undefined) { + delete changedAttributes[key] + } else { + changedAttributes[key] = currAttrVal + } + // current attributes doesn't change + previousAttributes[key] = value + } + } else { // !c.deleted + // content was inserted, and is possibly attributed + if (equalAttrs(value, currAttrVal)) { + // item.delete(transaction) + } else if (equalAttrs(value, previousAttributes[key] ?? null)) { + delete changedAttributes[key] + } else { + changedAttributes[key] = value + } + if (value == null) { + delete currentAttributes[key] + } else { + currentAttributes[key] = value + } + } + } else if (retainContent && !c.deleted) { + // fresh reference to currentAttributes only + if (usingCurrentAttributes) { + currentAttributes = object.assign({}, currentAttributes) + usingCurrentAttributes = false + } + if (usingChangedAttributes && changedAttributes[key] !== undefined) { + usingChangedAttributes = false + changedAttributes = object.assign({}, changedAttributes) + } + if (value == null) { + delete currentAttributes[key] + } else { + currentAttributes[key] = value + } + delete changedAttributes[key] + previousAttributes[key] = value + } + // # Update Attributions + if (attribution != null || object.hasProperty(previousUnattributedAttributes, key)) { + /** + * @type {import('../utils/AttributionManager.js').Attribution} + */ + const formattingAttribution = object.assign({}, d.usedAttribution) + const changedAttributedAttributes = /** @type {{ [key: string]: Array }} */ (formattingAttribution.format = object.assign({}, formattingAttribution.format ?? {})) + if (attribution == null || equalAttrs(previousUnattributedAttributes[key], currentAttributes[key] ?? null)) { + // an unattributed formatting attribute was found or an attributed formatting + // attribute was found that resets to the previous status + delete changedAttributedAttributes[key] + delete previousUnattributedAttributes[key] + } else { + const by = changedAttributedAttributes[key] = (changedAttributedAttributes[key]?.slice() ?? []) + by.push(...((c.deleted ? attribution.delete : attribution.insert) ?? [])) + const attributedAt = (c.deleted ? attribution.deletedAt : attribution.insertedAt) + if (attributedAt) formattingAttribution.formatAt = attributedAt + } + if (object.isEmpty(changedAttributedAttributes)) { + d.useAttribution(null) + } else if (attribution != null) { + const attributedAt = (c.deleted ? attribution.deletedAt : attribution.insertedAt) + if (attributedAt != null) formattingAttribution.formatAt = attributedAt + d.useAttribution(formattingAttribution) + } + } + break + } + } + } + } + } + return /** @type {any} */ (d.done()) + } + + /** + * Render the difference to another ydoc (which can be empty) and highlight the differences with + * attributions. + * + * @param {AbstractAttributionManager} am + * @return {ToDeepEventDelta} + */ + getContentDeep (am = noAttributionsManager) { + return /** @type {any} */ (this.getContent(am, { deep: true })) + } + + /** + * Apply a {@link Delta} on this shared type. + * + * @param {delta.Delta} d The changes to apply on this element. + * @param {AbstractAttributionManager} am + * + * @public + */ + applyDelta (d, am = noAttributionsManager) { + if (this.doc == null) { + (this._prelim || (this._prelim = /** @type {any} */ (delta.create()))).apply(d) + } else { + // @todo this was moved here from ytext. Make this more generic + transact(this.doc, transaction => { + const currPos = new ItemTextListPosition(null, this._start, 0, new Map(), am) + for (const op of d.children) { + if (delta.$textOp.check(op)) { + insertText(transaction, /** @type {any} */ (this), currPos, op.insert, op.format || {}) + } else if (delta.$insertOp.check(op)) { + for (let i = 0; i < op.insert.length; i++) { + let ins = op.insert[i] + if (delta.$deltaAny.check(ins)) { + if (ins.name != null) { + const t = new YXmlElement(ins.name) + t.applyDelta(ins) + ins = t + } else { + error.unexpectedCase() + } + } + insertText(transaction, /** @type {any} */ (this), currPos, ins, op.format || {}) + } + } else if (delta.$retainOp.check(op)) { + currPos.formatText(transaction, /** @type {any} */ (this), op.retain, op.format || {}) + } else if (delta.$deleteOp.check(op)) { + deleteText(transaction, currPos, op.delete) + } else if (delta.$modifyOp.check(op)) { + /** @type {ContentType} */ (currPos.right?.content).type.applyDelta(op.value) + currPos.formatText(transaction, /** @type {any} */ (this), 1, op.format || {}) + } else { + error.unexpectedCase() + } + } + for (const op of d.attrs) { + if (delta.$insertOp.check(op)) { + typeMapSet(transaction, /** @type {any} */ (this), op.key, op.value) + } else if (delta.$deleteOp.check(op)) { + typeMapDelete(transaction, /** @type {any} */ (this), op.key) + } else { + const sub = typeMapGet(/** @type {any} */ (this), op.key) + if (!(sub instanceof AbstractType)) { + error.unexpectedCase() + } + sub.applyDelta(op.value) + } + } + }) + } + } } +/** + * @param {any} a + * @param {any} b + * @return {boolean} + */ +export const equalAttrs = (a, b) => a === b || (typeof a === 'object' && typeof b === 'object' && a && b && object.equalFlat(a, b)) + +/** + * @template {delta.Delta} D + * @typedef {D extends delta.Delta + * ? delta.Delta< + * N, + * { [K in keyof Attrs]: TypeToDelta }, + * TypeToDelta, + * Text + * > + * : D + * } ToDeepEventDelta + */ + +/** + * @template {any} T + * @typedef {(Extract> extends AbstractType ? (unknown extends D ? never : ToDeepEventDelta) : never) | Exclude>} TypeToDelta + */ + /** * @param {AbstractType} type * @param {number} start @@ -405,6 +794,7 @@ export class AbstractType { * @function */ export const typeListSlice = (type, start, end) => { + type.doc ?? warnPrematureAccess() if (start < 0) { start = type._length + start } @@ -433,13 +823,14 @@ export const typeListSlice = (type, start, end) => { } /** - * @param {AbstractType} type + * @param {import('../utils/types.js').YType} type * @return {Array} * * @private * @function */ export const typeListToArray = type => { + type.doc ?? warnPrematureAccess() const cs = [] let n = type._start while (n !== null) { @@ -478,7 +869,7 @@ export const typeListToArraySnapshot = (type, snapshot) => { } /** - * Executes a provided function on once on overy element of this YArray. + * Executes a provided function on once on every element of this YArray. * * @param {AbstractType} type * @param {function(any,number,any):void} f A function to execute on every element of this YArray. @@ -489,6 +880,7 @@ export const typeListToArraySnapshot = (type, snapshot) => { export const typeListForEach = (type, f) => { let index = 0 let n = type._start + type.doc ?? warnPrematureAccess() while (n !== null) { if (n.countable && !n.deleted) { const c = n.content.getContent() @@ -521,7 +913,7 @@ export const typeListMap = (type, f) => { } /** - * @param {AbstractType} type + * @param {AbstractType} type * @return {IterableIterator} * * @private @@ -570,11 +962,11 @@ export const typeListCreateIterator = type => { } /** - * Executes a provided function on once on overy element of this YArray. + * Executes a provided function on once on every element of this YArray. * Operates on a snapshotted state of the document. * - * @param {AbstractType} type - * @param {function(any,number,AbstractType):void} f A function to execute on every element of this YArray. + * @param {AbstractType} type + * @param {function(any,number,AbstractType):void} f A function to execute on every element of this YArray. * @param {Snapshot} snapshot * * @private @@ -595,7 +987,7 @@ export const typeListForEachSnapshot = (type, f, snapshot) => { } /** - * @param {AbstractType} type + * @param {import('../utils/types.js').YType} type * @param {number} index * @return {any} * @@ -603,6 +995,7 @@ export const typeListForEachSnapshot = (type, f, snapshot) => { * @function */ export const typeListGet = (type, index) => { + type.doc ?? warnPrematureAccess() const marker = findMarker(type, index) let n = type._start if (marker !== null) { @@ -621,9 +1014,9 @@ export const typeListGet = (type, index) => { /** * @param {Transaction} transaction - * @param {AbstractType} parent + * @param {YType_} parent * @param {Item?} referenceItem - * @param {Array|Array|boolean|number|null|string|Uint8Array>} content + * @param {Array<_YValue>} content * * @private * @function @@ -655,6 +1048,8 @@ export const typeListInsertGenericsAfter = (transaction, parent, referenceItem, case Boolean: case Array: case String: + case BigInt: + case Date: jsonContent.push(c) break default: @@ -671,7 +1066,7 @@ export const typeListInsertGenericsAfter = (transaction, parent, referenceItem, break default: if (c instanceof AbstractType) { - left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentType(c)) + left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentType(/** @type {any} */ (c))) left.integrate(transaction, 0) } else { throw new Error('Unexpected content type in insert operation') @@ -683,11 +1078,11 @@ export const typeListInsertGenericsAfter = (transaction, parent, referenceItem, packJsonContent() } -const lengthExceeded = error.create('Length exceeded!') +const lengthExceeded = () => error.create('Length exceeded!') /** * @param {Transaction} transaction - * @param {AbstractType} parent + * @param {YType_} parent * @param {number} index * @param {Array|Array|number|null|string|Uint8Array>} content * @@ -696,7 +1091,7 @@ const lengthExceeded = error.create('Length exceeded!') */ export const typeListInsertGenerics = (transaction, parent, index, content) => { if (index > parent._length) { - throw lengthExceeded + throw lengthExceeded() } if (index === 0) { if (parent._searchMarker) { @@ -737,10 +1132,10 @@ export const typeListInsertGenerics = (transaction, parent, index, content) => { /** * Pushing content is special as we generally want to push after the last item. So we don't have to update - * the serach marker. + * the search marker. * * @param {Transaction} transaction - * @param {AbstractType} parent + * @param {YType_} parent * @param {Array|Array|number|null|string|Uint8Array>} content * * @private @@ -760,7 +1155,7 @@ export const typeListPushGenerics = (transaction, parent, content) => { /** * @param {Transaction} transaction - * @param {AbstractType} parent + * @param {import('../utils/types.js').YType} parent * @param {number} index * @param {number} length * @@ -798,7 +1193,7 @@ export const typeListDelete = (transaction, parent, index, length) => { n = n.right } if (length > 0) { - throw lengthExceeded + throw lengthExceeded() } if (parent._searchMarker) { updateMarkerChanges(parent._searchMarker, startIndex, -startLength + length /* in case we remove the above exception */) @@ -807,7 +1202,7 @@ export const typeListDelete = (transaction, parent, index, length) => { /** * @param {Transaction} transaction - * @param {AbstractType} parent + * @param {YType_} parent * @param {string} key * * @private @@ -822,9 +1217,9 @@ export const typeMapDelete = (transaction, parent, key) => { /** * @param {Transaction} transaction - * @param {AbstractType} parent + * @param {AbstractType} parent * @param {string} key - * @param {Object|number|null|Array|string|Uint8Array|AbstractType} value + * @param {_YValue} value * * @private * @function @@ -843,6 +1238,8 @@ export const typeMapSet = (transaction, parent, key, value) => { case Boolean: case Array: case String: + case Date: + case BigInt: content = new ContentAny([value]) break case Uint8Array: @@ -853,7 +1250,7 @@ export const typeMapSet = (transaction, parent, key, value) => { break default: if (value instanceof AbstractType) { - content = new ContentType(value) + content = new ContentType(/** @type {any} */ (value)) } else { throw new Error('Unexpected content type') } @@ -863,7 +1260,7 @@ export const typeMapSet = (transaction, parent, key, value) => { } /** - * @param {AbstractType} parent + * @param {AbstractType} parent * @param {string} key * @return {Object|number|null|Array|string|Uint8Array|AbstractType|undefined} * @@ -871,6 +1268,7 @@ export const typeMapSet = (transaction, parent, key, value) => { * @function */ export const typeMapGet = (parent, key) => { + parent.doc ?? warnPrematureAccess() const val = parent._map.get(key) return val !== undefined && !val.deleted ? val.content.getContent()[val.length - 1] : undefined } @@ -887,6 +1285,7 @@ export const typeMapGetAll = (parent) => { * @type {Object} */ const res = {} + parent.doc ?? warnPrematureAccess() parent._map.forEach((value, key) => { if (!value.deleted) { res[key] = value.content.getContent()[value.length - 1] @@ -895,6 +1294,66 @@ export const typeMapGetAll = (parent) => { return res } +/** + * Render the difference to another ydoc (which can be empty) and highlight the differences with + * attributions. + * + * Note that deleted content that was not deleted in prevYdoc is rendered as an insertion with the + * attribution `{ isDeleted: true, .. }`. + * + * @template {delta.DeltaBuilder} TypeDelta + * @param {TypeDelta} d + * @param {YType_} parent + * @param {Set?} attrsToRender + * @param {import('../internals.js').AbstractAttributionManager} am + * @param {boolean} deep + * @param {Set|Map|null} [modified] - set of types that should be rendered as modified children + * @param {import('../utils/IdSet.js').IdSet?} [deletedItems] + * @param {import('../utils/IdSet.js').IdSet?} [itemsToRender] + * + * @private + * @function + */ +export const typeMapGetDelta = (d, parent, attrsToRender, am, deep, modified, deletedItems, itemsToRender) => { + // @todo support modified ops! + /** + * @param {Item} item + * @param {string} key + */ + const renderAttrs = (item, key) => { + /** + * @type {Array>} + */ + const cs = [] + am.readContent(cs, item.id.client, item.id.clock, item.deleted, item.content, 1) + const { deleted, attrs, content } = cs[cs.length - 1] + const attribution = createAttributionFromAttributionItems(attrs, deleted) + let c = array.last(content.getContent()) + if (deleted) { + if (itemsToRender == null || itemsToRender.hasId(item.lastId)) { + d.unset(key, attribution, c) + } + } else { + // find prev content + let prevContentItem = item + // this algorithm is problematic. should check all previous content using am.readcontent + for (; prevContentItem.left !== null && deletedItems?.hasId(prevContentItem.left.lastId); prevContentItem = prevContentItem.left) { + // nop + } + const prevValue = (prevContentItem !== item && itemsToRender?.hasId(prevContentItem.lastId)) ? array.last(prevContentItem.content.getContent()) : undefined + if (deep && c instanceof AbstractType) { + c = /** @type {any} */(c).getContent(am) + } + d.set(key, c, attribution, prevValue) + } + } + if (attrsToRender == null) { + parent._map.forEach(renderAttrs) + } else { + attrsToRender.forEach(key => renderAttrs(/** @type {Item} */ (parent._map.get(key)), key)) + } +} + /** * @param {AbstractType} parent * @param {string} key @@ -904,6 +1363,7 @@ export const typeMapGetAll = (parent) => { * @function */ export const typeMapHas = (parent, key) => { + parent.doc ?? warnPrematureAccess() const val = parent._map.get(key) return val !== undefined && !val.deleted } @@ -926,10 +1386,41 @@ export const typeMapGetSnapshot = (parent, key, snapshot) => { } /** - * @param {Map} map + * @param {AbstractType} parent + * @param {Snapshot} snapshot + * @return {Object|number|null|Array|string|Uint8Array|AbstractType|undefined>} + * + * @private + * @function + */ +export const typeMapGetAllSnapshot = (parent, snapshot) => { + /** + * @type {Object} + */ + const res = {} + parent._map.forEach((value, key) => { + /** + * @type {Item|null} + */ + let v = value + while (v !== null && (!snapshot.sv.has(v.id.client) || v.id.clock >= (snapshot.sv.get(v.id.client) || 0))) { + v = v.left + } + if (v !== null && isVisible(v, snapshot)) { + res[key] = v.content.getContent()[v.length - 1] + } + }) + return res +} + +/** + * @param {AbstractType & { _map: Map }} type * @return {IterableIterator>} * * @private * @function */ -export const createMapIterator = map => iterator.iteratorFilter(map.entries(), /** @param {any} entry */ entry => !entry[1].deleted) +export const createMapIterator = type => { + type.doc ?? warnPrematureAccess() + return iterator.iteratorFilter(type._map.entries(), /** @param {any} entry */ entry => !entry[1].deleted) +} diff --git a/src/types/YArray.js b/src/types/YArray.js index 58c7287b2..eaad7db10 100644 --- a/src/types/YArray.js +++ b/src/types/YArray.js @@ -3,7 +3,6 @@ */ import { - YEvent, AbstractType, typeListGet, typeListToArray, @@ -14,34 +13,23 @@ import { typeListDelete, typeListMap, YArrayRefID, - callTypeObservers, transact, - ArraySearchMarker, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item // eslint-disable-line + warnPrematureAccess, + typeListSlice, + noAttributionsManager, + AbstractAttributionManager, ArraySearchMarker, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item // eslint-disable-line } from '../internals.js' -import { typeListSlice } from './AbstractType.js' -/** - * Event that describes the changes on a YArray - * @template T - * @extends YEvent> - */ -export class YArrayEvent extends YEvent { - /** - * @param {YArray} yarray The changed type - * @param {Transaction} transaction The transaction object - */ - constructor (yarray, transaction) { - super(yarray, transaction) - this._transaction = transaction - } -} +import * as delta from 'lib0/delta' // eslint-disable-line /** * A shared Array implementation. - * @template T - * @extends AbstractType> + * @template {import('../utils/types.js').YValue} T + * @extends {AbstractType,YArray>} * @implements {Iterable} */ +// @todo remove this +// @ts-ignore export class YArray extends AbstractType { constructor () { super() @@ -58,11 +46,14 @@ export class YArray extends AbstractType { /** * Construct a new YArray containing the specified items. - * @template T + * @template {import('../utils/types.js').YValue} T * @param {Array} items * @return {YArray} */ static from (items) { + /** + * @type {YArray} + */ const a = new YArray() a.push(items) return a @@ -76,7 +67,7 @@ export class YArray extends AbstractType { * * Observer functions are fired * * @param {Doc} y The Yjs instance - * @param {Item} item + * @param {Item?} item */ _integrate (y, item) { super._integrate(y, item) @@ -84,34 +75,28 @@ export class YArray extends AbstractType { this._prelimContent = null } - _copy () { - return new YArray() - } - /** + * Makes a copy of this data type that can be included somewhere else. + * + * Note that the content is only readable _after_ it has been included somewhere in the Ydoc. + * * @return {YArray} */ clone () { - const arr = new YArray() + /** + * @type {this} + */ + const arr = /** @type {this} */ (new YArray()) arr.insert(0, this.toArray().map(el => - el instanceof AbstractType ? el.clone() : el + // @ts-ignore + el instanceof AbstractType ? /** @type {any} */ (el.clone()) : el )) return arr } get length () { - return this._prelimContent === null ? this._length : this._prelimContent.length - } - - /** - * Creates YArrayEvent and calls observers. - * - * @param {Transaction} transaction - * @param {Set} parentSubs Keys changed on this type. `null` if list was modified. - */ - _callObserver (transaction, parentSubs) { - super._callObserver(transaction, parentSubs) - callTypeObservers(this, transaction, new YArrayEvent(this, transaction)) + this.doc ?? warnPrematureAccess() + return this._length } /** @@ -133,7 +118,7 @@ export class YArray extends AbstractType { insert (index, content) { if (this.doc !== null) { transact(this.doc, transaction => { - typeListInsertGenerics(transaction, this, index, content) + typeListInsertGenerics(transaction, this, index, /** @type {any} */ (content)) }) } else { /** @type {Array} */ (this._prelimContent).splice(index, 0, ...content) @@ -150,7 +135,7 @@ export class YArray extends AbstractType { push (content) { if (this.doc !== null) { transact(this.doc, transaction => { - typeListPushGenerics(transaction, this, content) + typeListPushGenerics(transaction, this, /** @type {any} */ (content)) }) } else { /** @type {Array} */ (this._prelimContent).push(...content) @@ -158,9 +143,9 @@ export class YArray extends AbstractType { } /** - * Preppends content to this YArray. + * Prepends content to this YArray. * - * @param {Array} content Array of content to preppend. + * @param {Array} content Array of content to prepend. */ unshift (content) { this.insert(0, content) @@ -202,7 +187,24 @@ export class YArray extends AbstractType { } /** - * Transforms this YArray to a JavaScript Array. + * Render the difference to another ydoc (which can be empty) and highlight the differences with + * attributions. + * + * Note that deleted content that was not deleted in prevYdoc is rendered as an insertion with the + * attribution `{ isDeleted: true, .. }`. + * + * @param {AbstractAttributionManager} am + * @return {delta.ArrayDelta>} The Delta representation of this type. + * + * @public + */ + getContentDeep (am = noAttributionsManager) { + return super.getContentDeep(am) + } + + /** + * Returns a portion of this YArray into a JavaScript Array selected + * from start to end (end not included). * * @param {number} [start] * @param {number} [end] @@ -235,7 +237,7 @@ export class YArray extends AbstractType { } /** - * Executes a provided function on once on overy element of this YArray. + * Executes a provided function once on every element of this YArray. * * @param {function(T,number,YArray):void} f A function to execute on every element of this YArray. */ @@ -259,9 +261,10 @@ export class YArray extends AbstractType { } /** - * @param {UpdateDecoderV1 | UpdateDecoderV2} decoder + * @param {UpdateDecoderV1 | UpdateDecoderV2} _decoder + * @return {import('../utils/types.js').YType} * * @private * @function */ -export const readYArray = decoder => new YArray() +export const readYArray = _decoder => new YArray() diff --git a/src/types/YMap.js b/src/types/YMap.js index a31b9e039..b610c1df3 100644 --- a/src/types/YMap.js +++ b/src/types/YMap.js @@ -1,10 +1,8 @@ - /** * @module YMap */ import { - YEvent, AbstractType, typeMapDelete, typeMapSet, @@ -12,36 +10,20 @@ import { typeMapHas, createMapIterator, YMapRefID, - callTypeObservers, transact, - UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item // eslint-disable-line + warnPrematureAccess, + UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Item // eslint-disable-line } from '../internals.js' import * as iterator from 'lib0/iterator' - -/** - * @template T - * @extends YEvent> - * Event that describes the changes on a YMap. - */ -export class YMapEvent extends YEvent { - /** - * @param {YMap} ymap The YArray that changed. - * @param {Transaction} transaction - * @param {Set} subs The keys that changed. - */ - constructor (ymap, transaction, subs) { - super(ymap, transaction) - this.keysChanged = subs - } -} +import * as delta from 'lib0/delta' // eslint-disable-line /** * @template MapType * A shared Map implementation. * - * @extends AbstractType> - * @implements {Iterable} + * @extends AbstractType> + * @implements {Iterable<[string, MapType]>} */ export class YMap extends AbstractType { /** @@ -71,7 +53,7 @@ export class YMap extends AbstractType { * * Observer functions are fired * * @param {Doc} y The Yjs instance - * @param {Item} item + * @param {Item?} item */ _integrate (y, item) { super._integrate(y, item) @@ -81,37 +63,28 @@ export class YMap extends AbstractType { this._prelimContent = null } - _copy () { - return new YMap() - } - /** - * @return {YMap} + * Makes a copy of this data type that can be included somewhere else. + * + * Note that the content is only readable _after_ it has been included somewhere in the Ydoc. + * + * @return {this} */ clone () { - const map = new YMap() + const map = this._copy() this.forEach((value, key) => { - map.set(key, value instanceof AbstractType ? value.clone() : value) + map.set(key, value instanceof AbstractType ? /** @type {typeof value} */ (value.clone()) : value) }) return map } - /** - * Creates YMapEvent and calls observers. - * - * @param {Transaction} transaction - * @param {Set} parentSubs Keys changed on this type. `null` if list was modified. - */ - _callObserver (transaction, parentSubs) { - callTypeObservers(this, transaction, new YMapEvent(this, transaction, parentSubs)) - } - /** * Transforms this Shared Type to a JSON object. * * @return {Object} */ toJSON () { + this.doc ?? warnPrematureAccess() /** * @type {Object} */ @@ -131,7 +104,7 @@ export class YMap extends AbstractType { * @return {number} */ get size () { - return [...createMapIterator(this._map)].length + return [...createMapIterator(this)].length } /** @@ -140,25 +113,25 @@ export class YMap extends AbstractType { * @return {IterableIterator} */ keys () { - return iterator.iteratorMap(createMapIterator(this._map), /** @param {any} v */ v => v[0]) + return iterator.iteratorMap(createMapIterator(this), /** @param {any} v */ v => v[0]) } /** * Returns the values for each element in the YMap Type. * - * @return {IterableIterator} + * @return {IterableIterator} */ values () { - return iterator.iteratorMap(createMapIterator(this._map), /** @param {any} v */ v => v[1].content.getContent()[v[1].length - 1]) + return iterator.iteratorMap(createMapIterator(this), /** @param {any} v */ v => v[1].content.getContent()[v[1].length - 1]) } /** * Returns an Iterator of [key, value] pairs * - * @return {IterableIterator} + * @return {IterableIterator<[string, MapType]>} */ entries () { - return iterator.iteratorMap(createMapIterator(this._map), /** @param {any} v */ v => [v[0], v[1].content.getContent()[v[1].length - 1]]) + return iterator.iteratorMap(createMapIterator(this), /** @param {any} v */ v => /** @type {any} */ ([v[0], v[1].content.getContent()[v[1].length - 1]])) } /** @@ -167,6 +140,7 @@ export class YMap extends AbstractType { * @param {function(MapType,string,YMap):void} f A function to execute on every element of this YArray. */ forEach (f) { + this.doc ?? warnPrematureAccess() this._map.forEach((item, key) => { if (!item.deleted) { f(item.content.getContent()[item.length - 1], key, this) @@ -177,7 +151,7 @@ export class YMap extends AbstractType { /** * Returns an Iterator of [key, value] pairs * - * @return {IterableIterator} + * @return {IterableIterator<[string, MapType]>} */ [Symbol.iterator] () { return this.entries() @@ -200,14 +174,16 @@ export class YMap extends AbstractType { /** * Adds or updates an element with a specified key and value. + * @template {MapType} VAL * * @param {string} key The key of the element to add to this YMap - * @param {MapType} value The value of the element to add + * @param {VAL} value The value of the element to add + * @return {VAL} */ set (key, value) { if (this.doc !== null) { transact(this.doc, transaction => { - typeMapSet(transaction, this, key, value) + typeMapSet(transaction, this, key, /** @type {any} */ (value)) }) } else { /** @type {Map} */ (this._prelimContent).set(key, value) @@ -241,7 +217,7 @@ export class YMap extends AbstractType { clear () { if (this.doc !== null) { transact(this.doc, transaction => { - this.forEach(function (value, key, map) { + this.forEach(function (_value, key, map) { typeMapDelete(transaction, map, key) }) }) @@ -259,9 +235,10 @@ export class YMap extends AbstractType { } /** - * @param {UpdateDecoderV1 | UpdateDecoderV2} decoder + * @param {UpdateDecoderV1 | UpdateDecoderV2} _decoder + * @return {import('../utils/types.js').YType} * * @private * @function */ -export const readYMap = decoder => new YMap() +export const readYMap = _decoder => new YMap() diff --git a/src/types/YText.js b/src/types/YText.js index 896fcdd43..3a45cd87d 100644 --- a/src/types/YText.js +++ b/src/types/YText.js @@ -1,25 +1,19 @@ - /** * @module YText */ import { - YEvent, AbstractType, getItemCleanStart, getState, - isVisible, createID, YTextRefID, - callTypeObservers, transact, ContentEmbed, GC, ContentFormat, ContentString, - splitSnapshotAffectedStructs, - iterateDeletedStructs, - iterateStructs, + iterateStructsByIdSet, findMarker, typeMapDelete, typeMapSet, @@ -27,32 +21,31 @@ import { typeMapGetAll, updateMarkerChanges, ContentType, - ArraySearchMarker, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, ID, Doc, Item, Snapshot, Transaction // eslint-disable-line + warnPrematureAccess, + noAttributionsManager, AbstractAttributionManager, ArraySearchMarker, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Item, Transaction, // eslint-disable-line + createIdSet, + equalAttrs } from '../internals.js' -import * as object from 'lib0/object' +import * as math from 'lib0/math' +import * as traits from 'lib0/traits' import * as map from 'lib0/map' import * as error from 'lib0/error' -/** - * @param {any} a - * @param {any} b - * @return {boolean} - */ -const equalAttrs = (a, b) => a === b || (typeof a === 'object' && typeof b === 'object' && a && b && object.equalFlat(a, b)) - export class ItemTextListPosition { /** * @param {Item|null} left * @param {Item|null} right * @param {number} index * @param {Map} currentAttributes + * @param {AbstractAttributionManager} am */ - constructor (left, right, index, currentAttributes) { + constructor (left, right, index, currentAttributes, am) { this.left = left this.right = right this.index = index this.currentAttributes = currentAttributes + this.am = am } /** @@ -69,14 +62,104 @@ export class ItemTextListPosition { } break default: - if (!this.right.deleted) { - this.index += this.right.length - } + this.index += this.am.contentLength(this.right) break } this.left = this.right this.right = this.right.right } + + /** + * @param {Transaction} transaction + * @param {import('../utils/types.js').YType} parent + * @param {number} length + * @param {Object} attributes + * + * @function + */ + formatText (transaction, parent, length, attributes) { + const doc = transaction.doc + const ownClientId = doc.clientID + minimizeAttributeChanges(this, attributes) + const negatedAttributes = insertAttributes(transaction, parent, this, attributes) + // iterate until first non-format or null is found + // delete all formats with attributes[format.key] != null + // also check the attributes after the first non-format as we do not want to insert redundant negated attributes there + // eslint-disable-next-line no-labels + iterationLoop: while ( + this.right !== null && + (length > 0 || + ( + negatedAttributes.size > 0 && + ((this.right.deleted && this.am.contentLength(this.right) === 0) || this.right.content.constructor === ContentFormat) + ) + ) + ) { + switch (this.right.content.constructor) { + case ContentFormat: { + if (!this.right.deleted) { + const { key, value } = /** @type {ContentFormat} */ (this.right.content) + const attr = attributes[key] + if (attr !== undefined) { + if (equalAttrs(attr, value)) { + negatedAttributes.delete(key) + } else { + if (length === 0) { + // no need to further extend negatedAttributes + // eslint-disable-next-line no-labels + break iterationLoop + } + negatedAttributes.set(key, value) + } + this.right.delete(transaction) + } else { + this.currentAttributes.set(key, value) + } + } + break + } + default: { + const item = this.right + const rightLen = this.am.contentLength(item) + if (length < rightLen) { + /** + * @type {Array>} + */ + const contents = [] + this.am.readContent(contents, item.id.client, item.id.clock, item.deleted, item.content, 0) + let i = 0 + for (; i < contents.length && length > 0; i++) { + const c = contents[i] + if ((!c.deleted || c.attrs != null) && c.content.isCountable()) { + length -= c.content.getLength() + } + } + if (length < 0 || (length === 0 && i !== contents.length)) { + const c = contents[--i] + getItemCleanStart(transaction, createID(item.id.client, c.clock + c.content.getLength() + length)) + } + } else { + length -= rightLen + } + break + } + } + this.forward() + } + // Quill just assumes that the editor starts with a newline and that it always + // ends with a newline. We only insert that newline when a new newline is + // inserted - i.e when length is bigger than type.length + if (length > 0) { + let newlines = '' + for (; length > 0; length--) { + newlines += '\n' + } + this.right = new Item(createID(ownClientId, getState(doc.store, ownClientId)), this.left, this.left && this.left.lastId, this.right, this.right && this.right.id, parent, null, new ContentString(newlines)) + this.right.integrate(transaction, 0) + this.forward() + } + insertNegatedAttributes(transaction, parent, this, negatedAttributes) + } } /** @@ -116,21 +199,22 @@ const findNextPosition = (transaction, pos, count) => { /** * @param {Transaction} transaction - * @param {AbstractType} parent + * @param {import('../utils/types.js').YType} parent * @param {number} index + * @param {boolean} useSearchMarker * @return {ItemTextListPosition} * * @private * @function */ -const findPosition = (transaction, parent, index) => { +const findPosition = (transaction, parent, index, useSearchMarker) => { const currentAttributes = new Map() - const marker = findMarker(parent, index) + const marker = useSearchMarker ? findMarker(parent, index) : null if (marker) { - const pos = new ItemTextListPosition(marker.p.left, marker.p, marker.index, currentAttributes) + const pos = new ItemTextListPosition(marker.p.left, marker.p, marker.index, currentAttributes, noAttributionsManager) return findNextPosition(transaction, pos, index - marker.index) } else { - const pos = new ItemTextListPosition(null, parent._start, 0, currentAttributes) + const pos = new ItemTextListPosition(null, parent._start, 0, currentAttributes, noAttributionsManager) return findNextPosition(transaction, pos, index) } } @@ -139,7 +223,7 @@ const findPosition = (transaction, parent, index) => { * Negate applied formats * * @param {Transaction} transaction - * @param {AbstractType} parent + * @param {import('../utils/types.js').YType} parent * @param {ItemTextListPosition} currPos * @param {Map} negatedAttributes * @@ -150,7 +234,7 @@ const insertNegatedAttributes = (transaction, parent, currPos, negatedAttributes // check if we really need to remove attributes while ( currPos.right !== null && ( - currPos.right.deleted === true || ( + (currPos.right.deleted && (currPos.am === noAttributionsManager || currPos.am.contentLength(currPos.right) === 0)) || ( currPos.right.content.constructor === ContentFormat && equalAttrs(negatedAttributes.get(/** @type {ContentFormat} */ (currPos.right.content).key), /** @type {ContentFormat} */ (currPos.right.content).value) ) @@ -201,7 +285,7 @@ const minimizeAttributeChanges = (currPos, attributes) => { while (true) { if (currPos.right === null) { break - } else if (currPos.right.deleted || (currPos.right.content.constructor === ContentFormat && equalAttrs(attributes[(/** @type {ContentFormat} */ (currPos.right.content)).key] || null, /** @type {ContentFormat} */ (currPos.right.content).value))) { + } else if (currPos.right.deleted ? (currPos.am.contentLength(currPos.right) === 0) : (!currPos.right.deleted && currPos.right.content.constructor === ContentFormat && equalAttrs(attributes[(/** @type {ContentFormat} */ (currPos.right.content)).key] ?? null, /** @type {ContentFormat} */ (currPos.right.content).value))) { // } else { break @@ -212,7 +296,7 @@ const minimizeAttributeChanges = (currPos, attributes) => { /** * @param {Transaction} transaction - * @param {AbstractType} parent + * @param {import('../utils/types.js').YType} parent * @param {ItemTextListPosition} currPos * @param {Object} attributes * @return {Map} @@ -227,7 +311,7 @@ const insertAttributes = (transaction, parent, currPos, attributes) => { // insert format-start items for (const key in attributes) { const val = attributes[key] - const currentVal = currPos.currentAttributes.get(key) || null + const currentVal = currPos.currentAttributes.get(key) ?? null if (!equalAttrs(currentVal, val)) { // save negated attribute (set null if currentVal undefined) negatedAttributes.set(key, currentVal) @@ -242,16 +326,16 @@ const insertAttributes = (transaction, parent, currPos, attributes) => { /** * @param {Transaction} transaction - * @param {AbstractType} parent + * @param {import('../utils/types.js').YType} parent * @param {ItemTextListPosition} currPos - * @param {string|object|AbstractType} text + * @param {string|object|import('../utils/types.js').YType} text * @param {Object} attributes * * @private * @function **/ -const insertText = (transaction, parent, currPos, text, attributes) => { - currPos.currentAttributes.forEach((val, key) => { +export const insertText = (transaction, parent, currPos, text, attributes) => { + currPos.currentAttributes.forEach((_val, key) => { if (attributes[key] === undefined) { attributes[key] = null } @@ -274,81 +358,6 @@ const insertText = (transaction, parent, currPos, text, attributes) => { insertNegatedAttributes(transaction, parent, currPos, negatedAttributes) } -/** - * @param {Transaction} transaction - * @param {AbstractType} parent - * @param {ItemTextListPosition} currPos - * @param {number} length - * @param {Object} attributes - * - * @private - * @function - */ -const formatText = (transaction, parent, currPos, length, attributes) => { - const doc = transaction.doc - const ownClientId = doc.clientID - minimizeAttributeChanges(currPos, attributes) - const negatedAttributes = insertAttributes(transaction, parent, currPos, attributes) - // iterate until first non-format or null is found - // delete all formats with attributes[format.key] != null - // also check the attributes after the first non-format as we do not want to insert redundant negated attributes there - // eslint-disable-next-line no-labels - iterationLoop: while ( - currPos.right !== null && - (length > 0 || - ( - negatedAttributes.size > 0 && - (currPos.right.deleted || currPos.right.content.constructor === ContentFormat) - ) - ) - ) { - if (!currPos.right.deleted) { - switch (currPos.right.content.constructor) { - case ContentFormat: { - const { key, value } = /** @type {ContentFormat} */ (currPos.right.content) - const attr = attributes[key] - if (attr !== undefined) { - if (equalAttrs(attr, value)) { - negatedAttributes.delete(key) - } else { - if (length === 0) { - // no need to further extend negatedAttributes - // eslint-disable-next-line no-labels - break iterationLoop - } - negatedAttributes.set(key, value) - } - currPos.right.delete(transaction) - } else { - currPos.currentAttributes.set(key, value) - } - break - } - default: - if (length < currPos.right.length) { - getItemCleanStart(transaction, createID(currPos.right.id.client, currPos.right.id.clock + length)) - } - length -= currPos.right.length - break - } - } - currPos.forward() - } - // Quill just assumes that the editor starts with a newline and that it always - // ends with a newline. We only insert that newline when a new newline is - // inserted - i.e when length is bigger than type.length - if (length > 0) { - let newlines = '' - for (; length > 0; length--) { - newlines += '\n' - } - currPos.right = new Item(createID(ownClientId, getState(doc.store, ownClientId)), currPos.left, currPos.left && currPos.left.lastId, currPos.right, currPos.right && currPos.right.id, parent, null, new ContentString(newlines)) - currPos.right.integrate(transaction, 0) - currPos.forward() - } - insertNegatedAttributes(transaction, parent, currPos, negatedAttributes) -} - /** * Call this function after string content has been deleted in order to * clean up formatting Items. @@ -363,33 +372,50 @@ const formatText = (transaction, parent, currPos, length, attributes) => { * @function */ const cleanupFormattingGap = (transaction, start, curr, startAttributes, currAttributes) => { - let end = curr - const endAttributes = map.copy(currAttributes) + if (!transaction.doc.cleanupFormatting) return 0 + /** + * @type {Item|null} + */ + let end = start + /** + * @type {Map} + */ + const endFormats = map.create() while (end && (!end.countable || end.deleted)) { if (!end.deleted && end.content.constructor === ContentFormat) { - updateCurrentAttributes(endAttributes, /** @type {ContentFormat} */ (end.content)) + const cf = /** @type {ContentFormat} */ (end.content) + endFormats.set(cf.key, cf) } end = end.right } let cleanups = 0 - let reachedEndOfCurr = false + let reachedCurr = false while (start !== end) { if (curr === start) { - reachedEndOfCurr = true + reachedCurr = true } if (!start.deleted) { const content = start.content switch (content.constructor) { case ContentFormat: { const { key, value } = /** @type {ContentFormat} */ (content) - if ((endAttributes.get(key) || null) !== value || (startAttributes.get(key) || null) === value) { + const startAttrValue = startAttributes.get(key) ?? null + if (endFormats.get(key) !== content || startAttrValue === value) { // Either this format is overwritten or it is not necessary because the attribute already existed. start.delete(transaction) + transaction.cleanUps.add(start.id.client, start.id.clock, start.length) cleanups++ - if (!reachedEndOfCurr && (currAttributes.get(key) || null) === value && (startAttributes.get(key) || null) !== value) { - currAttributes.delete(key) + if (!reachedCurr && (currAttributes.get(key) ?? null) === value && startAttrValue !== value) { + if (startAttrValue === null) { + currAttributes.delete(key) + } else { + currAttributes.set(key, startAttrValue) + } } } + if (!reachedCurr && !start.deleted) { + updateCurrentAttributes(currAttributes, /** @type {ContentFormat} */ (content)) + } break } } @@ -404,6 +430,7 @@ const cleanupFormattingGap = (transaction, start, curr, startAttributes, currAtt * @param {Item | null} item */ const cleanupContextlessFormattingGap = (transaction, item) => { + if (!transaction.doc.cleanupFormatting) return 0 // iterate until item.right is null or content while (item && item.right && (item.right.deleted || !item.right.countable)) { item = item.right @@ -415,6 +442,7 @@ const cleanupContextlessFormattingGap = (transaction, item) => { const key = /** @type {ContentFormat} */ (item.content).key if (attrs.has(key)) { item.delete(transaction) + transaction.cleanUps.add(item.id.client, item.id.clock, item.length) } else { attrs.add(key) } @@ -432,10 +460,11 @@ const cleanupContextlessFormattingGap = (transaction, item) => { * * This function won't be exported anymore as soon as there is confidence that the YText type works as intended. * - * @param {YText} type + * @param {YText} type * @return {number} How many formatting attributes have been cleaned up. */ export const cleanupYTextFormatting = type => { + if (!type.doc?.cleanupFormatting) return 0 let res = 0 transact(/** @type {Doc} */ (type.doc), transaction => { let start = /** @type {Item} */ (type._start) @@ -461,6 +490,50 @@ export const cleanupYTextFormatting = type => { return res } +/** + * This will be called by the transaction once the event handlers are called to potentially cleanup + * formatting attributes. + * + * @param {Transaction} transaction + */ +export const cleanupYTextAfterTransaction = transaction => { + /** + * @type {Set>} + */ + const needFullCleanup = new Set() + // check if another formatting item was inserted + const doc = transaction.doc + iterateStructsByIdSet(transaction, transaction.insertSet, (item) => { + if ( + !item.deleted && /** @type {Item} */ (item).content.constructor === ContentFormat && item.constructor !== GC + ) { + needFullCleanup.add(/** @type {any} */ (item).parent) + } + }) + // cleanup in a new transaction + transact(doc, (t) => { + iterateStructsByIdSet(transaction, transaction.deleteSet, item => { + if (item instanceof GC || !(/** @type {YText} */ (item.parent)._hasFormatting) || needFullCleanup.has(/** @type {YText} */ (item.parent))) { + return + } + const parent = /** @type {YText} */ (item.parent) + if (item.content.constructor === ContentFormat) { + needFullCleanup.add(parent) + } else { + // If no formatting attribute was inserted or deleted, we can make due with contextless + // formatting cleanups. + // Contextless: it is not necessary to compute currentAttributes for the affected position. + cleanupContextlessFormattingGap(t, item) + } + }) + // If a formatting item was inserted, we simply clean the whole type. + // We need to compute currentAttributes for the current position anyway. + for (const yText of needFullCleanup) { + cleanupYTextFormatting(yText) + } + }) +} + /** * @param {Transaction} transaction * @param {ItemTextListPosition} currPos @@ -470,12 +543,12 @@ export const cleanupYTextFormatting = type => { * @private * @function */ -const deleteText = (transaction, currPos, length) => { +export const deleteText = (transaction, currPos, length) => { const startLength = length const startAttrs = map.copy(currPos.currentAttributes) const start = currPos.right while (length > 0 && currPos.right !== null) { - if (currPos.right.deleted === false) { + if (!currPos.right.deleted) { switch (currPos.right.content.constructor) { case ContentType: case ContentEmbed: @@ -487,6 +560,29 @@ const deleteText = (transaction, currPos, length) => { currPos.right.delete(transaction) break } + } else if (currPos.am !== noAttributionsManager) { + const item = currPos.right + /** + * @type {Array>} + */ + const contents = [] + currPos.am.readContent(contents, item.id.client, item.id.clock, true, item.content, 0) + for (let i = 0; i < contents.length; i++) { + const c = contents[i] + if (c.content.isCountable() && c.attrs != null) { + // deleting already deleted content. store that information in a meta property, but do + // nothing + const contentLen = math.min(c.content.getLength(), length) + map.setIfUndefined(transaction.meta, 'attributedDeletes', createIdSet).add(item.id.client, c.clock, contentLen) + length -= contentLen + } + } + const lastContent = contents.length > 0 ? contents[contents.length - 1] : null + const nextItemClock = item.id.clock + item.length + const nextContentClock = lastContent != null ? lastContent.clock + lastContent.content.getLength() : nextItemClock + if (nextContentClock < nextItemClock) { + getItemCleanStart(transaction, createID(item.id.client, nextContentClock)) + } } currPos.forward() } @@ -502,7 +598,7 @@ const deleteText = (transaction, currPos, length) => { /** * The Quill Delta format represents changes on a text document with - * formatting information. For mor information visit {@link https://quilljs.com/docs/delta/|Quill Delta} + * formatting information. For more information visit {@link https://quilljs.com/docs/delta/|Quill Delta} * * @example * { @@ -527,249 +623,6 @@ const deleteText = (transaction, currPos, length) => { * @typedef {Object} TextAttributes */ -/** - * @extends YEvent - * Event that describes the changes on a YText type. - */ -export class YTextEvent extends YEvent { - /** - * @param {YText} ytext - * @param {Transaction} transaction - * @param {Set} subs The keys that changed - */ - constructor (ytext, transaction, subs) { - super(ytext, transaction) - /** - * Whether the children changed. - * @type {Boolean} - * @private - */ - this.childListChanged = false - /** - * Set of all changed attributes. - * @type {Set} - */ - this.keysChanged = new Set() - subs.forEach((sub) => { - if (sub === null) { - this.childListChanged = true - } else { - this.keysChanged.add(sub) - } - }) - } - - /** - * @type {{added:Set,deleted:Set,keys:Map,delta:Array<{insert?:Array|string, delete?:number, retain?:number}>}} - */ - get changes () { - if (this._changes === null) { - /** - * @type {{added:Set,deleted:Set,keys:Map,delta:Array<{insert?:Array|string|AbstractType|object, delete?:number, retain?:number}>}} - */ - const changes = { - keys: this.keys, - delta: this.delta, - added: new Set(), - deleted: new Set() - } - this._changes = changes - } - return /** @type {any} */ (this._changes) - } - - /** - * Compute the changes in the delta format. - * A {@link https://quilljs.com/docs/delta/|Quill Delta}) that represents the changes on the document. - * - * @type {Array<{insert?:string|object|AbstractType, delete?:number, retain?:number, attributes?: Object}>} - * - * @public - */ - get delta () { - if (this._delta === null) { - const y = /** @type {Doc} */ (this.target.doc) - /** - * @type {Array<{insert?:string|object|AbstractType, delete?:number, retain?:number, attributes?: Object}>} - */ - const delta = [] - transact(y, transaction => { - const currentAttributes = new Map() // saves all current attributes for insert - const oldAttributes = new Map() - let item = this.target._start - /** - * @type {string?} - */ - let action = null - /** - * @type {Object} - */ - const attributes = {} // counts added or removed new attributes for retain - /** - * @type {string|object} - */ - let insert = '' - let retain = 0 - let deleteLen = 0 - const addOp = () => { - if (action !== null) { - /** - * @type {any} - */ - let op - switch (action) { - case 'delete': - op = { delete: deleteLen } - deleteLen = 0 - break - case 'insert': - op = { insert } - if (currentAttributes.size > 0) { - op.attributes = {} - currentAttributes.forEach((value, key) => { - if (value !== null) { - op.attributes[key] = value - } - }) - } - insert = '' - break - case 'retain': - op = { retain } - if (Object.keys(attributes).length > 0) { - op.attributes = {} - for (const key in attributes) { - op.attributes[key] = attributes[key] - } - } - retain = 0 - break - } - delta.push(op) - action = null - } - } - while (item !== null) { - switch (item.content.constructor) { - case ContentType: - case ContentEmbed: - if (this.adds(item)) { - if (!this.deletes(item)) { - addOp() - action = 'insert' - insert = item.content.getContent()[0] - addOp() - } - } else if (this.deletes(item)) { - if (action !== 'delete') { - addOp() - action = 'delete' - } - deleteLen += 1 - } else if (!item.deleted) { - if (action !== 'retain') { - addOp() - action = 'retain' - } - retain += 1 - } - break - case ContentString: - if (this.adds(item)) { - if (!this.deletes(item)) { - if (action !== 'insert') { - addOp() - action = 'insert' - } - insert += /** @type {ContentString} */ (item.content).str - } - } else if (this.deletes(item)) { - if (action !== 'delete') { - addOp() - action = 'delete' - } - deleteLen += item.length - } else if (!item.deleted) { - if (action !== 'retain') { - addOp() - action = 'retain' - } - retain += item.length - } - break - case ContentFormat: { - const { key, value } = /** @type {ContentFormat} */ (item.content) - if (this.adds(item)) { - if (!this.deletes(item)) { - const curVal = currentAttributes.get(key) || null - if (!equalAttrs(curVal, value)) { - if (action === 'retain') { - addOp() - } - if (equalAttrs(value, (oldAttributes.get(key) || null))) { - delete attributes[key] - } else { - attributes[key] = value - } - } else if (value !== null) { - item.delete(transaction) - } - } - } else if (this.deletes(item)) { - oldAttributes.set(key, value) - const curVal = currentAttributes.get(key) || null - if (!equalAttrs(curVal, value)) { - if (action === 'retain') { - addOp() - } - attributes[key] = curVal - } - } else if (!item.deleted) { - oldAttributes.set(key, value) - const attr = attributes[key] - if (attr !== undefined) { - if (!equalAttrs(attr, value)) { - if (action === 'retain') { - addOp() - } - if (value === null) { - delete attributes[key] - } else { - attributes[key] = value - } - } else if (attr !== null) { // this will be cleaned up automatically by the contextless cleanup function - item.delete(transaction) - } - } - } - if (!item.deleted) { - if (action === 'insert') { - addOp() - } - updateCurrentAttributes(currentAttributes, /** @type {ContentFormat} */ (item.content)) - } - break - } - } - item = item.right - } - addOp() - while (delta.length > 0) { - const lastOp = delta[delta.length - 1] - if (lastOp.retain !== undefined && lastOp.attributes === undefined) { - // retain delta's if they don't assign attributes - delta.pop() - } else { - break - } - } - }) - this._delta = delta - } - return /** @type {any} */ (this._delta) - } -} - /** * Type that represents text with formatting information. * @@ -777,7 +630,8 @@ export class YTextEvent extends YEvent { * block formats (format information on a paragraph), embeds (complex elements * like pictures and videos), and text formats (**bold**, *italic*). * - * @extends AbstractType + * @template {{ [key:string]:any } | import('../utils/types.js').YType} [Embeds={ [key:string]:any } | import('../utils/types.js').YType] + * @extends {AbstractType>} */ export class YText extends AbstractType { /** @@ -791,9 +645,14 @@ export class YText extends AbstractType { */ this._pending = string !== undefined ? [() => this.insert(0, string)] : [] /** - * @type {Array} + * @type {Array|null} */ this._searchMarker = [] + /** + * Whether this YText contains formatting attributes. + * This flag is updated when a formatting item is integrated (see ContentFormat.integrate) + */ + this._hasFormatting = false } /** @@ -802,12 +661,13 @@ export class YText extends AbstractType { * @type {number} */ get length () { + this.doc ?? warnPrematureAccess() return this._length } /** * @param {Doc} y - * @param {Item} item + * @param {Item?} item */ _integrate (y, item) { super._integrate(y, item) @@ -819,16 +679,19 @@ export class YText extends AbstractType { this._pending = null } - _copy () { - return new YText() - } - /** - * @return {YText} + * Makes a copy of this data type that can be included somewhere else. + * + * Note that the content is only readable _after_ it has been included somewhere in the Ydoc. + * + * @return {YText} */ clone () { - const text = new YText() - text.applyDelta(this.toDelta()) + /** + * @type {YText} + */ + const text = /** @type {any} */ (new YText()) + text.applyDelta(this.getContent()) return text } @@ -840,56 +703,9 @@ export class YText extends AbstractType { */ _callObserver (transaction, parentSubs) { super._callObserver(transaction, parentSubs) - const event = new YTextEvent(this, transaction, parentSubs) - const doc = transaction.doc - callTypeObservers(this, transaction, event) // If a remote change happened, we try to cleanup potential formatting duplicates. - if (!transaction.local) { - // check if another formatting item was inserted - let foundFormattingItem = false - for (const [client, afterClock] of transaction.afterState.entries()) { - const clock = transaction.beforeState.get(client) || 0 - if (afterClock === clock) { - continue - } - iterateStructs(transaction, /** @type {Array} */ (doc.store.clients.get(client)), clock, afterClock, item => { - if (!item.deleted && /** @type {Item} */ (item).content.constructor === ContentFormat) { - foundFormattingItem = true - } - }) - if (foundFormattingItem) { - break - } - } - if (!foundFormattingItem) { - iterateDeletedStructs(transaction, transaction.deleteSet, item => { - if (item instanceof GC || foundFormattingItem) { - return - } - if (item.parent === this && item.content.constructor === ContentFormat) { - foundFormattingItem = true - } - }) - } - transact(doc, (t) => { - if (foundFormattingItem) { - // If a formatting item was inserted, we simply clean the whole type. - // We need to compute currentAttributes for the current position anyway. - cleanupYTextFormatting(this) - } else { - // If no formatting attribute was inserted, we can make due with contextless - // formatting cleanups. - // Contextless: it is not necessary to compute currentAttributes for the affected position. - iterateDeletedStructs(t, t.deleteSet, item => { - if (item instanceof GC) { - return - } - if (item.parent === this) { - cleanupContextlessFormattingGap(t, item) - } - }) - } - }) + if (!transaction.local && this._hasFormatting) { + transaction._needFormattingCleanup = true } } @@ -899,6 +715,7 @@ export class YText extends AbstractType { * @public */ toString () { + this.doc ?? warnPrematureAccess() let str = '' /** * @type {Item|null} @@ -923,151 +740,6 @@ export class YText extends AbstractType { return this.toString() } - /** - * Apply a {@link Delta} on this shared YText type. - * - * @param {any} delta The changes to apply on this element. - * @param {object} [opts] - * @param {boolean} [opts.sanitize] Sanitize input delta. Removes ending newlines if set to true. - * - * - * @public - */ - applyDelta (delta, { sanitize = true } = {}) { - if (this.doc !== null) { - transact(this.doc, transaction => { - const currPos = new ItemTextListPosition(null, this._start, 0, new Map()) - for (let i = 0; i < delta.length; i++) { - const op = delta[i] - if (op.insert !== undefined) { - // Quill assumes that the content starts with an empty paragraph. - // Yjs/Y.Text assumes that it starts empty. We always hide that - // there is a newline at the end of the content. - // If we omit this step, clients will see a different number of - // paragraphs, but nothing bad will happen. - const ins = (!sanitize && typeof op.insert === 'string' && i === delta.length - 1 && currPos.right === null && op.insert.slice(-1) === '\n') ? op.insert.slice(0, -1) : op.insert - if (typeof ins !== 'string' || ins.length > 0) { - insertText(transaction, this, currPos, ins, op.attributes || {}) - } - } else if (op.retain !== undefined) { - formatText(transaction, this, currPos, op.retain, op.attributes || {}) - } else if (op.delete !== undefined) { - deleteText(transaction, currPos, op.delete) - } - } - }) - } else { - /** @type {Array} */ (this._pending).push(() => this.applyDelta(delta)) - } - } - - /** - * Returns the Delta representation of this YText type. - * - * @param {Snapshot} [snapshot] - * @param {Snapshot} [prevSnapshot] - * @param {function('removed' | 'added', ID):any} [computeYChange] - * @return {any} The Delta representation of this type. - * - * @public - */ - toDelta (snapshot, prevSnapshot, computeYChange) { - /** - * @type{Array} - */ - const ops = [] - const currentAttributes = new Map() - const doc = /** @type {Doc} */ (this.doc) - let str = '' - let n = this._start - function packStr () { - if (str.length > 0) { - // pack str with attributes to ops - /** - * @type {Object} - */ - const attributes = {} - let addAttributes = false - currentAttributes.forEach((value, key) => { - addAttributes = true - attributes[key] = value - }) - /** - * @type {Object} - */ - const op = { insert: str } - if (addAttributes) { - op.attributes = attributes - } - ops.push(op) - str = '' - } - } - // snapshots are merged again after the transaction, so we need to keep the - // transalive until we are done - transact(doc, transaction => { - if (snapshot) { - splitSnapshotAffectedStructs(transaction, snapshot) - } - if (prevSnapshot) { - splitSnapshotAffectedStructs(transaction, prevSnapshot) - } - while (n !== null) { - if (isVisible(n, snapshot) || (prevSnapshot !== undefined && isVisible(n, prevSnapshot))) { - switch (n.content.constructor) { - case ContentString: { - const cur = currentAttributes.get('ychange') - if (snapshot !== undefined && !isVisible(n, snapshot)) { - if (cur === undefined || cur.user !== n.id.client || cur.type !== 'removed') { - packStr() - currentAttributes.set('ychange', computeYChange ? computeYChange('removed', n.id) : { type: 'removed' }) - } - } else if (prevSnapshot !== undefined && !isVisible(n, prevSnapshot)) { - if (cur === undefined || cur.user !== n.id.client || cur.type !== 'added') { - packStr() - currentAttributes.set('ychange', computeYChange ? computeYChange('added', n.id) : { type: 'added' }) - } - } else if (cur !== undefined) { - packStr() - currentAttributes.delete('ychange') - } - str += /** @type {ContentString} */ (n.content).str - break - } - case ContentType: - case ContentEmbed: { - packStr() - /** - * @type {Object} - */ - const op = { - insert: n.content.getContent()[0] - } - if (currentAttributes.size > 0) { - const attrs = /** @type {Object} */ ({}) - op.attributes = attrs - currentAttributes.forEach((value, key) => { - attrs[key] = value - }) - } - ops.push(op) - break - } - case ContentFormat: - if (isVisible(n, snapshot)) { - packStr() - updateCurrentAttributes(currentAttributes, /** @type {ContentFormat} */ (n.content)) - } - break - } - } - n = n.right - } - packStr() - }, splitSnapshotAffectedStructs) - return ops - } - /** * Insert text at a given index. * @@ -1085,7 +757,7 @@ export class YText extends AbstractType { const y = this.doc if (y !== null) { transact(y, transaction => { - const pos = findPosition(transaction, this, index) + const pos = findPosition(transaction, this, index, !attributes) if (!attributes) { attributes = {} // @ts-ignore @@ -1103,20 +775,20 @@ export class YText extends AbstractType { * * @param {number} index The index to insert the embed at. * @param {Object | AbstractType} embed The Object that represents the embed. - * @param {TextAttributes} attributes Attribute information to apply on the + * @param {TextAttributes} [attributes] Attribute information to apply on the * embed * * @public */ - insertEmbed (index, embed, attributes = {}) { + insertEmbed (index, embed, attributes) { const y = this.doc if (y !== null) { transact(y, transaction => { - const pos = findPosition(transaction, this, index) - insertText(transaction, this, pos, embed, attributes) + const pos = findPosition(transaction, this, index, !attributes) + insertText(transaction, this, pos, embed, attributes || {}) }) } else { - /** @type {Array} */ (this._pending).push(() => this.insertEmbed(index, embed, attributes)) + /** @type {Array} */ (this._pending).push(() => this.insertEmbed(index, embed, attributes || {})) } } @@ -1135,7 +807,7 @@ export class YText extends AbstractType { const y = this.doc if (y !== null) { transact(y, transaction => { - deleteText(transaction, findPosition(transaction, this, index), length) + deleteText(transaction, findPosition(transaction, this, index, true), length) }) } else { /** @type {Array} */ (this._pending).push(() => this.delete(index, length)) @@ -1159,11 +831,11 @@ export class YText extends AbstractType { const y = this.doc if (y !== null) { transact(y, transaction => { - const pos = findPosition(transaction, this, index) + const pos = findPosition(transaction, this, index, false) if (pos.right === null) { return } - formatText(transaction, this, pos, length, attributes) + pos.formatText(transaction, this, length, attributes) }) } else { /** @type {Array} */ (this._pending).push(() => this.format(index, length, attributes)) @@ -1229,12 +901,11 @@ export class YText extends AbstractType { * * @note Xml-Text nodes don't have attributes. You can use this feature to assign properties to complete text-blocks. * - * @param {Snapshot} [snapshot] * @return {Object} A JSON Object that describes the attributes. * * @public */ - getAttributes (snapshot) { + getAttributes () { return typeMapGetAll(this) } @@ -1244,13 +915,20 @@ export class YText extends AbstractType { _write (encoder) { encoder.writeTypeRef(YTextRefID) } + + /** + * @param {this} other + */ + [traits.EqualityTraitSymbol] (other) { + return this.getContent().equals(other.getContent()) + } } /** - * @param {UpdateDecoderV1 | UpdateDecoderV2} decoder - * @return {YText} + * @param {UpdateDecoderV1 | UpdateDecoderV2} _decoder + * @return {import('../utils/types.js').YType} * * @private * @function */ -export const readYText = decoder => new YText() +export const readYText = _decoder => new YText() diff --git a/src/types/YXmlElement.js b/src/types/YXmlElement.js index 464951b4f..83a3aa4ac 100644 --- a/src/types/YXmlElement.js +++ b/src/types/YXmlElement.js @@ -1,3 +1,4 @@ +import * as object from 'lib0/object' import { YXmlFragment, @@ -7,17 +8,25 @@ import { typeMapSet, typeMapGet, typeMapGetAll, - typeListForEach, + typeMapGetAllSnapshot, YXmlElementRefID, - YXmlText, ContentType, AbstractType, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Snapshot, Doc, Item // eslint-disable-line + Snapshot, YXmlText, ContentType, AbstractType, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Item, // eslint-disable-line } from '../internals.js' +/** + * @typedef {Object|number|null|Array|string|Uint8Array|AbstractType} ValueTypes + */ + /** * An YXmlElement imitates the behavior of a - * {@link https://developer.mozilla.org/en-US/docs/Web/API/Element|Dom Element}. + * https://developer.mozilla.org/en-US/docs/Web/API/Element|Dom Element * * * An YXmlElement has attributes (key value pairs) * * An YXmlElement has childElements that must inherit from YXmlElement + * + * @template {{ [key: string]: any }} [Attrs={ [key: string]: string }] + * @template {any} [Children=any] + * @extends YXmlFragment */ export class YXmlElement extends YXmlFragment { constructor (nodeName = 'UNDEFINED') { @@ -53,7 +62,7 @@ export class YXmlElement extends YXmlFragment { * * Observer functions are fired * * @param {Doc} y The Yjs instance - * @param {Item} item + * @param {Item?} item */ _integrate (y, item) { super._integrate(y, item) @@ -66,21 +75,27 @@ export class YXmlElement extends YXmlFragment { /** * Creates an Item with the same effect as this Item (without position effect) * - * @return {YXmlElement} + * @return {this} */ _copy () { - return new YXmlElement(this.nodeName) + return /** @type {any} */ (new YXmlElement(this.nodeName)) } /** - * @return {YXmlElement} + * Makes a copy of this data type that can be included somewhere else. + * + * Note that the content is only readable _after_ it has been included somewhere in the Ydoc. + * + * @return {this} */ clone () { - const el = new YXmlElement(this.nodeName) + const el = this._copy() const attrs = this.getAttributes() - for (const key in attrs) { - el.setAttribute(key, attrs[key]) - } + object.forEach(attrs, (value, key) => { + if (typeof value === 'string') { + el.setAttribute(key, value) + } + }) // @ts-ignore el.insert(0, this.toArray().map(item => item instanceof AbstractType ? item.clone() : item)) return el @@ -116,7 +131,7 @@ export class YXmlElement extends YXmlFragment { /** * Removes an attribute from this YXmlElement. * - * @param {String} attributeName The attribute name that is to be removed. + * @param {string} attributeName The attribute name that is to be removed. * * @public */ @@ -133,15 +148,17 @@ export class YXmlElement extends YXmlFragment { /** * Sets or updates an attribute. * - * @param {String} attributeName The attribute name that is to be set. - * @param {String} attributeValue The attribute value that is to be set. + * @template {keyof Attrs & string} KEY + * + * @param {KEY} attributeName The attribute name that is to be set. + * @param {Attrs[KEY]} attributeValue The attribute value that is to be set. * * @public */ setAttribute (attributeName, attributeValue) { if (this.doc !== null) { transact(this.doc, transaction => { - typeMapSet(transaction, this, attributeName, attributeValue) + typeMapSet(transaction, this, attributeName, /** @type {any} */ (attributeValue)) }) } else { /** @type {Map} */ (this._prelimAttrs).set(attributeName, attributeValue) @@ -151,9 +168,11 @@ export class YXmlElement extends YXmlFragment { /** * Returns an attribute value that belongs to the attribute name. * - * @param {String} attributeName The attribute name that identifies the + * @template {keyof Attrs & string} KEY + * + * @param {KEY} attributeName The attribute name that identifies the * queried value. - * @return {String} The queried attribute value. + * @return {Attrs[KEY]|undefined} The queried attribute value. * * @public */ @@ -164,7 +183,7 @@ export class YXmlElement extends YXmlFragment { /** * Returns whether an attribute exists * - * @param {String} attributeName The attribute name to check for existence. + * @param {string} attributeName The attribute name to check for existence. * @return {boolean} whether the attribute exists. * * @public @@ -177,42 +196,12 @@ export class YXmlElement extends YXmlFragment { * Returns all attribute name/value pairs in a JSON Object. * * @param {Snapshot} [snapshot] - * @return {Object} A JSON Object that describes the attributes. + * @return {{ [Key in Extract]?: Attrs[Key]}} A JSON Object that describes the attributes. * * @public */ getAttributes (snapshot) { - return typeMapGetAll(this) - } - - /** - * Creates a Dom Element that mirrors this YXmlElement. - * - * @param {Document} [_document=document] The document object (you must define - * this when calling this method in - * nodejs) - * @param {Object} [hooks={}] Optional property to customize how hooks - * are presented in the DOM - * @param {any} [binding] You should not set this property. This is - * used if DomBinding wants to create a - * association to the created DOM type. - * @return {Node} The {@link https://developer.mozilla.org/en-US/docs/Web/API/Element|Dom Element} - * - * @public - */ - toDOM (_document = document, hooks = {}, binding) { - const dom = _document.createElement(this.nodeName) - const attrs = this.getAttributes() - for (const key in attrs) { - dom.setAttribute(key, attrs[key]) - } - typeListForEach(this, yxml => { - dom.appendChild(yxml.toDOM(_document, hooks, binding)) - }) - if (binding !== undefined) { - binding._createAssociation(dom, this) - } - return dom + return /** @type {any} */ (snapshot ? typeMapGetAllSnapshot(this, snapshot) : typeMapGetAll(this)) } /** @@ -231,7 +220,7 @@ export class YXmlElement extends YXmlFragment { /** * @param {UpdateDecoderV1 | UpdateDecoderV2} decoder - * @return {YXmlElement} + * @return {import('../utils/types.js').YType} * * @function */ diff --git a/src/types/YXmlEvent.js b/src/types/YXmlEvent.js deleted file mode 100644 index 3c2566ed4..000000000 --- a/src/types/YXmlEvent.js +++ /dev/null @@ -1,40 +0,0 @@ - -import { - YEvent, - YXmlText, YXmlElement, YXmlFragment, Transaction // eslint-disable-line -} from '../internals.js' - -/** - * @extends YEvent - * An Event that describes changes on a YXml Element or Yxml Fragment - */ -export class YXmlEvent extends YEvent { - /** - * @param {YXmlElement|YXmlText|YXmlFragment} target The target on which the event is created. - * @param {Set} subs The set of changed attributes. `null` is included if the - * child list changed. - * @param {Transaction} transaction The transaction instance with wich the - * change was created. - */ - constructor (target, subs, transaction) { - super(target, transaction) - /** - * Whether the children changed. - * @type {Boolean} - * @private - */ - this.childListChanged = false - /** - * Set of all changed attributes. - * @type {Set} - */ - this.attributesChanged = new Set() - subs.forEach((sub) => { - if (sub === null) { - this.childListChanged = true - } else { - this.attributesChanged.add(sub) - } - }) - } -} diff --git a/src/types/YXmlFragment.js b/src/types/YXmlFragment.js index 8c4622362..be65a48ff 100644 --- a/src/types/YXmlFragment.js +++ b/src/types/YXmlFragment.js @@ -3,8 +3,6 @@ */ import { - YXmlEvent, - YXmlElement, AbstractType, typeListMap, typeListForEach, @@ -13,13 +11,14 @@ import { typeListDelete, typeListToArray, YXmlFragmentRefID, - callTypeObservers, transact, typeListGet, typeListSlice, - UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, ContentType, Transaction, Item, YXmlText, YXmlHook, Snapshot // eslint-disable-line + warnPrematureAccess, + YXmlElement, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item, YXmlText, YXmlHook // eslint-disable-line } from '../internals.js' +import * as delta from 'lib0/delta' // eslint-disable-line import * as error from 'lib0/error' /** @@ -43,78 +42,6 @@ import * as error from 'lib0/error' * @return {boolean} Whether to include the Dom node in the YXmlElement. */ -/** - * Represents a subset of the nodes of a YXmlElement / YXmlFragment and a - * position within them. - * - * Can be created with {@link YXmlFragment#createTreeWalker} - * - * @public - * @implements {Iterable} - */ -export class YXmlTreeWalker { - /** - * @param {YXmlFragment | YXmlElement} root - * @param {function(AbstractType):boolean} [f] - */ - constructor (root, f = () => true) { - this._filter = f - this._root = root - /** - * @type {Item} - */ - this._currentNode = /** @type {Item} */ (root._start) - this._firstCall = true - } - - [Symbol.iterator] () { - return this - } - - /** - * Get the next node. - * - * @return {IteratorResult} The next node. - * - * @public - */ - next () { - /** - * @type {Item|null} - */ - let n = this._currentNode - let type = n && n.content && /** @type {any} */ (n.content).type - if (n !== null && (!this._firstCall || n.deleted || !this._filter(type))) { // if first call, we check if we can use the first item - do { - type = /** @type {any} */ (n.content).type - if (!n.deleted && (type.constructor === YXmlElement || type.constructor === YXmlFragment) && type._start !== null) { - // walk down in the tree - n = type._start - } else { - // walk right or up in the tree - while (n !== null) { - if (n.right !== null) { - n = n.right - break - } else if (n.parent === this._root) { - n = null - } else { - n = /** @type {AbstractType} */ (n.parent)._item - } - } - } - } while (n !== null && (n.deleted || !this._filter(/** @type {ContentType} */ (n.content).type))) - } - this._firstCall = false - if (n === null) { - // @ts-ignore - return { value: undefined, done: true } - } - this._currentNode = n - return { value: /** @type {any} */ (n.content).type, done: false } - } -} - /** * Represents a list of {@link YXmlElement}.and {@link YXmlText} types. * A YxmlFragment is similar to a {@link YXmlElement}, but it does not have a @@ -122,12 +49,15 @@ export class YXmlTreeWalker { * element - in this case the attributes and the nodeName are not shared. * * @public - * @extends AbstractType + * @template {any} [Children=any] + * @template {{[K in string]:any}} [Attrs={}] + * @extends AbstractType> */ export class YXmlFragment extends AbstractType { constructor () { super() /** + * @todo remove _prelimContent * @type {Array|null} */ this._prelimContent = [] @@ -149,7 +79,7 @@ export class YXmlFragment extends AbstractType { * * Observer functions are fired * * @param {Doc} y The Yjs instance - * @param {Item} item + * @param {Item?} item */ _integrate (y, item) { super._integrate(y, item) @@ -157,99 +87,24 @@ export class YXmlFragment extends AbstractType { this._prelimContent = null } - _copy () { - return new YXmlFragment() - } - /** - * @return {YXmlFragment} + * Makes a copy of this data type that can be included somewhere else. + * + * Note that the content is only readable _after_ it has been included somewhere in the Ydoc. + * + * @return {this} */ clone () { - const el = new YXmlFragment() - // @ts-ignore + const el = this._copy() el.insert(0, this.toArray().map(item => item instanceof AbstractType ? item.clone() : item)) return el } get length () { + this.doc ?? warnPrematureAccess() return this._prelimContent === null ? this._length : this._prelimContent.length } - /** - * Create a subtree of childNodes. - * - * @example - * const walker = elem.createTreeWalker(dom => dom.nodeName === 'div') - * for (let node in walker) { - * // `node` is a div node - * nop(node) - * } - * - * @param {function(AbstractType):boolean} filter Function that is called on each child element and - * returns a Boolean indicating whether the child - * is to be included in the subtree. - * @return {YXmlTreeWalker} A subtree and a position within it. - * - * @public - */ - createTreeWalker (filter) { - return new YXmlTreeWalker(this, filter) - } - - /** - * Returns the first YXmlElement that matches the query. - * Similar to DOM's {@link querySelector}. - * - * Query support: - * - tagname - * TODO: - * - id - * - attribute - * - * @param {CSS_Selector} query The query on the children. - * @return {YXmlElement|YXmlText|YXmlHook|null} The first element that matches the query or null. - * - * @public - */ - querySelector (query) { - query = query.toUpperCase() - // @ts-ignore - const iterator = new YXmlTreeWalker(this, element => element.nodeName && element.nodeName.toUpperCase() === query) - const next = iterator.next() - if (next.done) { - return null - } else { - return next.value - } - } - - /** - * Returns all YXmlElements that match the query. - * Similar to Dom's {@link querySelectorAll}. - * - * @todo Does not yet support all queries. Currently only query by tagName. - * - * @param {CSS_Selector} query The query on the children - * @return {Array} The elements that match this query. - * - * @public - */ - querySelectorAll (query) { - query = query.toUpperCase() - // @ts-ignore - return Array.from(new YXmlTreeWalker(this, element => element.nodeName && element.nodeName.toUpperCase() === query)) - } - - /** - * Creates YXmlEvent and calls observers. - * - * @param {Transaction} transaction - * @param {Set} parentSubs Keys changed on this type. `null` if list was modified. - */ - _callObserver (transaction, parentSubs) { - callTypeObservers(this, transaction, new YXmlEvent(this, parentSubs, transaction)) - } - /** * Get the string representation of all the children of this YXmlFragment. * @@ -266,32 +121,6 @@ export class YXmlFragment extends AbstractType { return this.toString() } - /** - * Creates a Dom Element that mirrors this YXmlElement. - * - * @param {Document} [_document=document] The document object (you must define - * this when calling this method in - * nodejs) - * @param {Object} [hooks={}] Optional property to customize how hooks - * are presented in the DOM - * @param {any} [binding] You should not set this property. This is - * used if DomBinding wants to create a - * association to the created DOM type. - * @return {Node} The {@link https://developer.mozilla.org/en-US/docs/Web/API/Element|Dom Element} - * - * @public - */ - toDOM (_document = document, hooks = {}, binding) { - const fragment = _document.createDocumentFragment() - if (binding !== undefined) { - binding._createAssociation(fragment, this) - } - typeListForEach(this, xmlType => { - fragment.insertBefore(xmlType.toDOM(_document, hooks, binding), null) - }) - return fragment - } - /** * Inserts new content at an index. * @@ -300,7 +129,7 @@ export class YXmlFragment extends AbstractType { * xml.insert(0, [new Y.XmlText('text')]) * * @param {number} index The index to insert content at - * @param {Array} content The array of content + * @param {Array} content The array of content */ insert (index, content) { if (this.doc !== null) { @@ -375,9 +204,9 @@ export class YXmlFragment extends AbstractType { } /** - * Preppends content to this YArray. + * Prepends content to this YArray. * - * @param {Array} content Array of content to preppend. + * @param {Array} content Array of content to prepend. */ unshift (content) { this.insert(0, content) @@ -394,7 +223,8 @@ export class YXmlFragment extends AbstractType { } /** - * Transforms this YArray to a JavaScript Array. + * Returns a portion of this YXmlFragment into a JavaScript Array selected + * from start to end (end not included). * * @param {number} [start] * @param {number} [end] @@ -405,9 +235,9 @@ export class YXmlFragment extends AbstractType { } /** - * Executes a provided function on once on overy child element. + * Executes a provided function on once on every child element. * - * @param {function(YXmlElement|YXmlText,number, typeof this):void} f A function to execute on every element of this YArray. + * @param {function(YXmlElement|YXmlText,number, typeof self):void} f A function to execute on every element of this YArray. */ forEach (f) { typeListForEach(this, f) @@ -427,10 +257,10 @@ export class YXmlFragment extends AbstractType { } /** - * @param {UpdateDecoderV1 | UpdateDecoderV2} decoder - * @return {YXmlFragment} + * @param {UpdateDecoderV1 | UpdateDecoderV2} _decoder + * @return {import('../utils/types.js').YType} * * @private * @function */ -export const readYXmlFragment = decoder => new YXmlFragment() +export const readYXmlFragment = _decoder => new YXmlFragment() diff --git a/src/types/YXmlHook.js b/src/types/YXmlHook.js index be8c759b6..13e49be25 100644 --- a/src/types/YXmlHook.js +++ b/src/types/YXmlHook.js @@ -1,4 +1,3 @@ - import { YMap, YXmlHookRefID, @@ -23,53 +22,27 @@ export class YXmlHook extends YMap { } /** - * Creates an Item with the same effect as this Item (without position effect) + * @return {this} */ _copy () { - return new YXmlHook(this.hookName) + return /** @type {this} */ (new YXmlHook(this.hookName)) } /** - * @return {YXmlHook} + * Makes a copy of this data type that can be included somewhere else. + * + * Note that the content is only readable _after_ it has been included somewhere in the Ydoc. + * + * @return {this} */ clone () { - const el = new YXmlHook(this.hookName) + const el = this._copy() this.forEach((value, key) => { el.set(key, value) }) return el } - /** - * Creates a Dom Element that mirrors this YXmlElement. - * - * @param {Document} [_document=document] The document object (you must define - * this when calling this method in - * nodejs) - * @param {Object.} [hooks] Optional property to customize how hooks - * are presented in the DOM - * @param {any} [binding] You should not set this property. This is - * used if DomBinding wants to create a - * association to the created DOM type - * @return {Element} The {@link https://developer.mozilla.org/en-US/docs/Web/API/Element|Dom Element} - * - * @public - */ - toDOM (_document = document, hooks = {}, binding) { - const hook = hooks[this.hookName] - let dom - if (hook !== undefined) { - dom = hook.createDom(this) - } else { - dom = document.createElement(this.hookName) - } - dom.setAttribute('data-yjs-hook', this.hookName) - if (binding !== undefined) { - binding._createAssociation(dom, this) - } - return dom - } - /** * Transform the properties of this type to binary and write it to an * BinaryEncoder. @@ -86,7 +59,7 @@ export class YXmlHook extends YMap { /** * @param {UpdateDecoderV1 | UpdateDecoderV2} decoder - * @return {YXmlHook} + * @return {import('../utils/types.js').YType} * * @private * @function diff --git a/src/types/YXmlText.js b/src/types/YXmlText.js index 470ce70f1..8f66ad11c 100644 --- a/src/types/YXmlText.js +++ b/src/types/YXmlText.js @@ -1,4 +1,3 @@ - import { YText, YXmlTextRefID, @@ -6,8 +5,11 @@ import { } from '../internals.js' /** + * @todo can we deprecate this? + * * Represents text in a Dom Element. In the future this type will also handle * simple formatting information like bold and italic. + * @extends YText */ export class YXmlText extends YText { /** @@ -26,74 +28,17 @@ export class YXmlText extends YText { return n ? /** @type {YXmlElement|YXmlText} */ (/** @type {ContentType} */ (n.content).type) : null } - _copy () { - return new YXmlText() - } - - /** - * @return {YXmlText} - */ - clone () { - const text = new YXmlText() - text.applyDelta(this.toDelta()) - return text - } - /** - * Creates a Dom Element that mirrors this YXmlText. + * Makes a copy of this data type that can be included somewhere else. * - * @param {Document} [_document=document] The document object (you must define - * this when calling this method in - * nodejs) - * @param {Object} [hooks] Optional property to customize how hooks - * are presented in the DOM - * @param {any} [binding] You should not set this property. This is - * used if DomBinding wants to create a - * association to the created DOM type. - * @return {Text} The {@link https://developer.mozilla.org/en-US/docs/Web/API/Element|Dom Element} + * Note that the content is only readable _after_ it has been included somewhere in the Ydoc. * - * @public + * @return {this} */ - toDOM (_document = document, hooks, binding) { - const dom = _document.createTextNode(this.toString()) - if (binding !== undefined) { - binding._createAssociation(dom, this) - } - return dom - } - - toString () { - // @ts-ignore - return this.toDelta().map(delta => { - const nestedNodes = [] - for (const nodeName in delta.attributes) { - const attrs = [] - for (const key in delta.attributes[nodeName]) { - attrs.push({ key, value: delta.attributes[nodeName][key] }) - } - // sort attributes to get a unique order - attrs.sort((a, b) => a.key < b.key ? -1 : 1) - nestedNodes.push({ nodeName, attrs }) - } - // sort node order to get a unique order - nestedNodes.sort((a, b) => a.nodeName < b.nodeName ? -1 : 1) - // now convert to dom string - let str = '' - for (let i = 0; i < nestedNodes.length; i++) { - const node = nestedNodes[i] - str += `<${node.nodeName}` - for (let j = 0; j < node.attrs.length; j++) { - const attr = node.attrs[j] - str += ` ${attr.key}="${attr.value}"` - } - str += '>' - } - str += delta.insert - for (let i = nestedNodes.length - 1; i >= 0; i--) { - str += `` - } - return str - }).join('') + clone () { + const text = /** @type {this} */ (this._copy()) + text.applyDelta(this.getContent()) + return text } /** @@ -112,10 +57,10 @@ export class YXmlText extends YText { } /** - * @param {UpdateDecoderV1 | UpdateDecoderV2} decoder - * @return {YXmlText} + * @param {UpdateDecoderV1 | UpdateDecoderV2} _decoder + * @return {import('../utils/types.js').YType} * * @private * @function */ -export const readYXmlText = decoder => new YXmlText() +export const readYXmlText = _decoder => new YXmlText() diff --git a/src/utils/AbstractConnector.js b/src/utils/AbstractConnector.js index ecf76a3bb..f5c0566a6 100644 --- a/src/utils/AbstractConnector.js +++ b/src/utils/AbstractConnector.js @@ -1,5 +1,4 @@ - -import { Observable } from 'lib0/observable' +import { ObservableV2 } from 'lib0/observable' import { Doc // eslint-disable-line @@ -11,9 +10,9 @@ import { * @note This interface is experimental and it is not advised to actually inherit this class. * It just serves as typing information. * - * @extends {Observable} + * @extends {ObservableV2} */ -export class AbstractConnector extends Observable { +export class AbstractConnector extends ObservableV2 { /** * @param {Doc} ydoc * @param {any} awareness diff --git a/src/utils/AttributionManager.js b/src/utils/AttributionManager.js new file mode 100644 index 000000000..3bfb4278f --- /dev/null +++ b/src/utils/AttributionManager.js @@ -0,0 +1,619 @@ +import { + getItem, + diffIdSet, + createInsertSetFromStructStore, + createDeleteSetFromStructStore, + createIdMapFromIdSet, + ContentDeleted, + insertIntoIdMap, + insertIntoIdSet, + diffIdMap, + createIdMap, + createAttributionItem, + mergeIdMaps, + createID, + mergeIdSets, + applyUpdate, + writeIdSet, + UpdateEncoderV1, + transact, + createMaybeAttrRange, + createIdSet, + writeStructsFromIdSet, + UndoManager, + StackItem, + getItemCleanStart, + intersectSets, + ContentFormat, + StructStore, Transaction, ID, IdSet, Item, Snapshot, Doc, AbstractContent, IdMap // eslint-disable-line +} from '../internals.js' + +import * as error from 'lib0/error' +import { ObservableV2 } from 'lib0/observable' +import * as encoding from 'lib0/encoding' +import * as s from 'lib0/schema' + +export const attributionJsonSchema = s.$object({ + insert: s.$array(s.$string).optional, + insertedAt: s.$number.optional, + delete: s.$array(s.$string).optional, + deletedAt: s.$number.optional, + format: s.$record(s.$string, s.$array(s.$string)).optional, + formatAt: s.$number.optional +}) + +/** + * @todo rename this to `insertBy`, `insertAt`, .. + * + * @typedef {s.Unwrap} Attribution + */ + +/** + * @todo SHOULD NOT RETURN AN OBJECT! + * @param {Array>?} attrs + * @param {boolean} deleted - whether the attributed item is deleted + * @return {Attribution?} + */ +export const createAttributionFromAttributionItems = (attrs, deleted) => { + if (attrs == null) { + return null + } + /** + * @type {Attribution} + */ + const attribution = {} + if (deleted) { + attribution.delete = s.$array(s.$string).cast([]) + } else { + attribution.insert = [] + } + attrs.forEach(attr => { + switch (attr.name) { + // eslint-disable-next-line no-fallthrough + case 'insert': + case 'delete': { + const as = /** @type {import('lib0/delta').Attribution} */ (attribution) + const ls = as[attr.name] = as[attr.name] ?? [] + ls.push(attr.val) + break + } + default: { + if (attr.name[0] !== '_') { + /** @type {any} */ (attribution)[attr.name] = attr.val + } + } + } + }) + return attribution +} + +/** + * @template T + */ +export class AttributedContent { + /** + * @param {AbstractContent} content + * @param {number} clock + * @param {boolean} deleted + * @param {Array> | null} attrs + * @param {0|1|2} renderBehavior + */ + constructor (content, clock, deleted, attrs, renderBehavior) { + this.content = content + this.clock = clock + this.deleted = deleted + this.attrs = attrs + this.render = renderBehavior === 0 ? false : (renderBehavior === 1 ? (!deleted || attrs != null) : true) + } +} + +/** + * Abstract class for associating Attributions to content / changes + * + * Should fire an event when the attributions changed _after_ the original change happens. This + * Event will be used to update the attribution on the current content. + * + * @extends {ObservableV2<{change:(idset:IdSet,origin:any,local:boolean)=>void}>} + */ +export class AbstractAttributionManager extends ObservableV2 { + /** + * @param {Array>} _contents - where to write the result + * @param {number} _client + * @param {number} _clock + * @param {boolean} _deleted + * @param {AbstractContent} _content + * @param {0|1|2} _shouldRender - 0: if undeleted or attributed, render as a retain operation. 1: render only if undeleted or attributed. 2: render as insert operation (if unattributed and deleted, render as delete). + */ + readContent (_contents, _client, _clock, _deleted, _content, _shouldRender) { + error.methodUnimplemented() + } + + /** + * Calculate the length of the attributed content. This is used by iterators that walk through the + * content. + * + * If the content is not countable, it should return 0. + * + * @param {Item} _item + * @return {number} + */ + contentLength (_item) { + error.methodUnimplemented() + } +} + +/** + * @implements AbstractAttributionManager + * + * @extends {ObservableV2<{change:(idset:IdSet,origin:any,local:boolean)=>void}>} + */ +export class TwosetAttributionManager extends ObservableV2 { + /** + * @param {IdMap} inserts + * @param {IdMap} deletes + */ + constructor (inserts, deletes) { + super() + this.inserts = inserts + this.deletes = deletes + } + + /** + * @param {Array>} contents - where to write the result + * @param {number} client + * @param {number} clock + * @param {boolean} deleted + * @param {AbstractContent} content + * @param {0|1|2} shouldRender - whether this should render or just result in a `retain` operation + */ + readContent (contents, client, clock, deleted, content, shouldRender) { + const slice = (deleted ? this.deletes : this.inserts).slice(client, clock, content.getLength()) + content = slice.length === 1 ? content : content.copy() + slice.forEach(s => { + const c = content + if (s.len < c.getLength()) { + content = c.splice(s.len) + } + if (!deleted || s.attrs != null || shouldRender) { + contents.push(new AttributedContent(c, s.clock, deleted, s.attrs, shouldRender)) + } + }) + } + + /** + * @param {Item} item + * @return {number} + */ + contentLength (item) { + if (!item.content.isCountable()) { + return 0 + } else if (!item.deleted) { + return item.length + } else { + return this.deletes.sliceId(item.id, item.length).reduce((len, s) => s.attrs != null ? len + s.len : len, 0) + } + } +} + +/** + * Abstract class for associating Attributions to content / changes + * + * @implements AbstractAttributionManager + * + * @extends {ObservableV2<{change:(idset:IdSet,origin:any,local:boolean)=>void}>} + */ +export class NoAttributionsManager extends ObservableV2 { + /** + * @param {Array>} contents - where to write the result + * @param {number} _client + * @param {number} clock + * @param {boolean} deleted + * @param {AbstractContent} content + * @param {0|1|2} shouldRender - whether this should render or just result in a `retain` operation + */ + readContent (contents, _client, clock, deleted, content, shouldRender) { + if (!deleted || shouldRender) { + contents.push(new AttributedContent(content, clock, deleted, null, shouldRender)) + } + } + + /** + * @param {Item} item + * @return {number} + */ + contentLength (item) { + return (item.deleted || !item.content.isCountable()) ? 0 : item.length + } +} + +export const noAttributionsManager = new NoAttributionsManager() + +/** + * @param {StructStore} store + * @param {number} client + * @param {number} clock + * @param {number} len + */ +const getItemContent = (store, client, clock, len) => { + // Retrieved item is never more fragmented than the newer item. + const prevItem = getItem(store, createID(client, clock)) + const diffStart = clock - prevItem.id.clock + let content = prevItem.length > 1 ? prevItem.content.copy() : prevItem.content + // trim itemContent to the correct size. + if (diffStart > 0) { + content = content.splice(diffStart) + } + if (len < content.getLength()) { + content.splice(len) + } + return content +} + +/** + * @param {Transaction?} tr - only specify this if you want to fill the content of deleted content + * @param {DiffAttributionManager} am + * @param {ID} start + * @param {ID} end + * @param {boolean} collectAll - collect as many items as possible. Accept adding redundant changes. + */ +const collectSuggestedChanges = (tr, am, start, end, collectAll) => { + const inserts = createIdSet() + const deletes = createIdSet() + const store = am._nextDoc.store + /** + * make sure to collect suggestions until all formats are closed + * @type {Set} + */ + const openedCollectedFormats = new Set() + /** + * @type {Item?} + */ + let item = getItem(store, start) + const endItem = start === end ? item : (end == null ? null : getItem(store, end)) + + // walk to the left and find first un-attributed change that is rendered + while (item.left != null) { + item = item.left + if (item.content instanceof ContentFormat && item.content.value == null) { + item = item.right + break + } + if (!item.deleted) { + const slice = am.inserts.slice(item.id.client, item.id.clock, item.length) + if (slice.some(s => s.attrs === null)) { + for (let i = slice.length - 1; i >= 0; i--) { + const s = slice[i] + if (s.attrs == null) break + inserts.add(item.id.client, s.clock, s.len) + } + item = item.right + break + } + } + } + let foundEndItem = false + // eslint-disable-next-line + itemLoop: while (item != null) { + const itemClient = item.id.client + const slice = (item.deleted ? am.deletes : am.inserts).slice(itemClient, item.id.clock, item.length) + foundEndItem ||= item === endItem + if (item.deleted) { + // item probably gc'd content. Need to split item and fill with content again + for (let i = slice.length - 1; i >= 0; i--) { + const s = slice[i] + if (s.attrs != null || collectAll) { + deletes.add(itemClient, s.clock, s.len) + if (collectAll) { + // in case item has been added and deleted this might be necessary. the forked document + // will automatically filter this if it doesn't have it already. + inserts.add(itemClient, s.clock, s.len) + } + } + if (tr != null) { + const splicedItem = getItemCleanStart(tr, createID(itemClient, s.clock)) + if (s.attrs != null) { + splicedItem.content = getItemContent(am._prevDocStore, itemClient, s.clock, s.len) + } + } + } + } else { + if (item.content instanceof ContentFormat) { + const { key, value } = item.content + if (value == null) { + openedCollectedFormats.delete(key) + } else { + openedCollectedFormats.add(key) + } + } + for (let i = 0; i < slice.length; i++) { + const s = slice[i] + if (s.attrs != null) { + inserts.add(itemClient, s.clock, s.len) + } else if (foundEndItem && openedCollectedFormats.size === 0) { + // eslint-disable-next-line + break itemLoop + } + } + } + item = item.right + } + return { inserts, deletes } +} + +/** + * @implements AbstractAttributionManager + * + * @extends {ObservableV2<{change:(idset:IdSet,origin:any,local:boolean)=>void}>} + */ +export class DiffAttributionManager extends ObservableV2 { + /** + * @param {Doc} prevDoc + * @param {Doc} nextDoc + */ + constructor (prevDoc, nextDoc) { + super() + const _nextDocInserts = createInsertSetFromStructStore(nextDoc.store, false) // unmaintained + const _prevDocInserts = createInsertSetFromStructStore(prevDoc.store, false) // unmaintained + const nextDocDeletes = createDeleteSetFromStructStore(nextDoc.store) // maintained + const prevDocDeletes = createDeleteSetFromStructStore(prevDoc.store) // maintained + this.inserts = createIdMapFromIdSet(diffIdSet(_nextDocInserts, _prevDocInserts), []) + this.deletes = createIdMapFromIdSet(diffIdSet(nextDocDeletes, prevDocDeletes), []) + this._prevDoc = prevDoc + this._prevDocStore = prevDoc.store + this._nextDoc = nextDoc + // update before observer calls fired + this._nextBOH = nextDoc.on('beforeObserverCalls', tr => { + // update inserts + const diffInserts = diffIdSet(tr.insertSet, _prevDocInserts) + insertIntoIdMap(this.inserts, createIdMapFromIdSet(diffInserts, [])) + // update deletes + const diffDeletes = diffIdSet(diffIdSet(tr.deleteSet, prevDocDeletes), this.inserts) + insertIntoIdMap(this.deletes, createIdMapFromIdSet(diffDeletes, [])) + // @todo fire update ranges on `diffInserts` and `diffDeletes` + }) + this._prevBOH = prevDoc.on('beforeObserverCalls', tr => { + insertIntoIdSet(_prevDocInserts, tr.insertSet) + insertIntoIdSet(prevDocDeletes, tr.deleteSet) + // insertIntoIdMap(this.inserts, createIdMapFromIdSet(intersectSets(tr.insertSet, this.inserts), [createAttributionItem('acceptInsert', 'unknown')])) + if (tr.insertSet.clients.size < 2) { + tr.insertSet.forEach((attrRange, client) => { + this.inserts.delete(client, attrRange.clock, attrRange.len) + }) + } else { + this.inserts = diffIdMap(this.inserts, tr.insertSet) + } + // insertIntoIdMap(this.deletes, createIdMapFromIdSet(intersectSets(tr.deleteSet, this.deletes), [createAttributionItem('acceptDelete', 'unknown')])) + if (tr.deleteSet.clients.size < 2) { + tr.deleteSet.forEach((attrRange, client) => { + this.deletes.delete(client, attrRange.clock, attrRange.len) + }) + } else { + this.deletes = diffIdMap(this.deletes, tr.deleteSet) + } + // fire event of "changed" attributions. exclude items that were added & deleted in the same + // transaction + this.emit('change', [diffIdSet(mergeIdSets([tr.insertSet, tr.deleteSet]), intersectSets(tr.insertSet, tr.deleteSet)), tr.origin, tr.local]) + }) + // changes from prevDoc should always flow into suggestionDoc + // changes from suggestionDoc only flow into ydoc if suggestion-mode is disabled + this._prevUpdateListener = prevDoc.on('update', (update, origin) => { + origin !== this && applyUpdate(nextDoc, update) + }) + this._ndUpdateListener = nextDoc.on('update', (update, origin, _doc, tr) => { + // only if event is local and suggestion mode is enabled + if (!this.suggestionMode && tr.local && (this.suggestionOrigins == null || this.suggestionOrigins.some(o => o === origin))) { + applyUpdate(prevDoc, update, this) + } + }) + this._afterTrListener = nextDoc.on('afterTransaction', (tr) => { + // apply deletes on attributed deletes (content that is already deleted, but is rendered by + // the attribution manager) + if (!this.suggestionMode && tr.local && (this.suggestionOrigins == null || this.suggestionOrigins.some(o => o === tr.origin))) { + const attributedDeletes = tr.meta.get('attributedDeletes') + if (attributedDeletes != null) { + transact(prevDoc, () => { + // apply attributed deletes if there are any + const ds = new UpdateEncoderV1() + encoding.writeVarUint(ds.restEncoder, 0) // encode 0 structs + writeIdSet(ds, attributedDeletes) + applyUpdate(prevDoc, ds.toUint8Array()) + }, this) + } + } + }) + this.suggestionMode = true + /** + * Optionally limit origins that may sync changes to the main doc if suggestion-mode is + * disabled. + * + * @type {Array?} + */ + this.suggestionOrigins = null + this._destroyHandler = nextDoc.on('destroy', this.destroy.bind(this)) + prevDoc.on('destroy', this._destroyHandler) + } + + destroy () { + super.destroy() + this._nextDoc.off('destroy', this._destroyHandler) + this._prevDoc.off('destroy', this._destroyHandler) + this._nextDoc.off('beforeObserverCalls', this._nextBOH) + this._prevDoc.off('beforeObserverCalls', this._prevBOH) + this._prevDoc.off('update', this._prevUpdateListener) + this._nextDoc.off('update', this._ndUpdateListener) + this._nextDoc.off('afterTransaction', this._afterTrListener) + } + + /** + * @param {ID} start + * @param {ID} end + */ + acceptChanges (start, end = start) { + const { inserts, deletes } = collectSuggestedChanges(null, this, start, end, true) + const encoder = new UpdateEncoderV1() + writeStructsFromIdSet(encoder, this._nextDoc.store, inserts) + writeIdSet(encoder, deletes) + applyUpdate(this._prevDoc, encoder.toUint8Array()) + } + + /** + * @param {ID} start + * @param {ID} end + */ + rejectChanges (start, end = start) { + this._nextDoc.transact(tr => { + const { inserts, deletes } = collectSuggestedChanges(tr, this, start, end, false) + const encoder = new UpdateEncoderV1() + writeStructsFromIdSet(encoder, this._nextDoc.store, inserts) + writeIdSet(encoder, deletes) + const um = new UndoManager(this._nextDoc) + um.undoStack.push(new StackItem(deletes, inserts)) + um.undo() + um.destroy() + }) + this.acceptChanges(start, end) + } + + /** + * @param {Array>} contents - where to write the result + * @param {number} client + * @param {number} clock + * @param {boolean} deleted + * @param {AbstractContent} _content + * @param {0|1|2} shouldRender - whether this should render or just result in a `retain` operation + */ + readContent (contents, client, clock, deleted, _content, shouldRender) { + const slice = (deleted ? this.deletes : this.inserts).slice(client, clock, _content.getLength()) + /** + * @type {AbstractContent?} + */ + let content = slice.length === 1 ? _content : _content.copy() + for (let i = 0; i < slice.length; i++) { + const s = slice[i] + if (content == null || content instanceof ContentDeleted) { + if ((!shouldRender && s.attrs == null) || this.inserts.has(client, s.clock)) { + continue + } + // Retrieved item is never more fragmented than the newer item. + const prevItem = getItem(this._prevDocStore, createID(client, s.clock)) + const diffStart = s.clock - prevItem.id.clock + content = prevItem.length > 1 ? prevItem.content.copy() : prevItem.content + // trim itemContent to the correct size. + if (diffStart > 0) { + content = content.splice(diffStart) + } + } + const c = /** @type {AbstractContent} */ (content) + const clen = c.getLength() + if (clen < s.len) { + slice.splice(i + 1, 0, createMaybeAttrRange(s.clock + clen, s.len - clen, s.attrs)) + s.len = clen + } + content = s.len < clen ? c.splice(s.len) : null + if (shouldRender || !deleted || s.attrs != null) { + contents.push(new AttributedContent(c, s.clock, deleted, s.attrs, shouldRender)) + } + } + } + + /** + * @param {Item} item + * @return {number} + */ + contentLength (item) { + if (!item.deleted) { + return item.content.isCountable() ? item.length : 0 + } + /** + * @type {Array>} + */ + const cs = [] + this.readContent(cs, item.id.client, item.id.clock, true, item.content, 0) + return cs.reduce((cnt, c) => cnt + ((c.attrs != null && c.content.isCountable()) ? c.content.getLength() : 0), 0) + } +} + +/** + * Attribute changes from ydoc1 to ydoc2. + * + * @param {Doc} prevDoc + * @param {Doc} nextDoc + */ +export const createAttributionManagerFromDiff = (prevDoc, nextDoc) => new DiffAttributionManager(prevDoc, nextDoc) + +/** + * Intended for projects that used the v13 snapshot feature. With this AttributionManager you can + * read content similar to the previous snapshot api. Requires that `ydoc.gc` is turned off. + * + * @implements AbstractAttributionManager + * + * @extends {ObservableV2<{change:(idset:IdSet,origin:any,local:boolean)=>void}>} + */ +export class SnapshotAttributionManager extends ObservableV2 { + /** + * @param {Snapshot} prevSnapshot + * @param {Snapshot} nextSnapshot + */ + constructor (prevSnapshot, nextSnapshot) { + super() + this.prevSnapshot = prevSnapshot + this.nextSnapshot = nextSnapshot + const inserts = createIdMap() + const deletes = createIdMapFromIdSet(diffIdSet(nextSnapshot.ds, prevSnapshot.ds), [createAttributionItem('change', '')]) + nextSnapshot.sv.forEach((clock, client) => { + const prevClock = prevSnapshot.sv.get(client) || 0 + inserts.add(client, 0, prevClock, []) // content is included in prevSnapshot is rendered without attributes + inserts.add(client, prevClock, clock - prevClock, [createAttributionItem('change', '')]) // content is rendered as "inserted" + }) + this.attrs = mergeIdMaps([diffIdMap(inserts, prevSnapshot.ds), deletes]) + } + + /** + * @param {Array>} contents - where to write the result + * @param {number} client + * @param {number} clock + * @param {boolean} _deleted + * @param {AbstractContent} content + * @param {0|1|2} shouldRender - whether this should render or just result in a `retain` operation + */ + readContent (contents, client, clock, _deleted, content, shouldRender) { + if ((this.nextSnapshot.sv.get(client) ?? 0) <= clock) return // future item that should not be displayed + const slice = this.attrs.slice(client, clock, content.getLength()) + content = slice.length === 1 ? content : content.copy() + slice.forEach(s => { + const deleted = this.nextSnapshot.ds.has(client, s.clock) + const nonExistend = (this.nextSnapshot.sv.get(client) ?? 0) <= s.clock + const c = content + if (s.len < c.getLength()) { + content = c.splice(s.len) + } + if (nonExistend) return + if (shouldRender || !deleted || (s.attrs != null && s.attrs.length > 0)) { + let attrsWithoutChange = s.attrs?.filter(attr => attr.name !== 'change') ?? null + if (s.attrs?.length === 0) { + attrsWithoutChange = null + } + contents.push(new AttributedContent(c, s.clock, deleted, attrsWithoutChange, shouldRender)) + } + }) + } + + /** + * @param {Item} item + * @return {number} + */ + contentLength (item) { + return item.content.isCountable() + ? (item.deleted + ? this.attrs.sliceId(item.id, item.length).reduce((len, s) => s.attrs != null ? len + s.len : len, 0) + : item.length + ) + : 0 + } +} + +/** + * @param {Snapshot} prevSnapshot + * @param {Snapshot} nextSnapshot + */ +export const createAttributionManagerFromSnapshots = (prevSnapshot, nextSnapshot = prevSnapshot) => new SnapshotAttributionManager(prevSnapshot, nextSnapshot) diff --git a/src/utils/DeleteSet.js b/src/utils/DeleteSet.js deleted file mode 100644 index d396c97bf..000000000 --- a/src/utils/DeleteSet.js +++ /dev/null @@ -1,326 +0,0 @@ - -import { - findIndexSS, - getState, - splitItem, - iterateStructs, - UpdateEncoderV2, - DSDecoderV1, DSEncoderV1, DSDecoderV2, DSEncoderV2, Item, GC, StructStore, Transaction, ID // eslint-disable-line -} from '../internals.js' - -import * as array from 'lib0/array' -import * as math from 'lib0/math' -import * as map from 'lib0/map' -import * as encoding from 'lib0/encoding' -import * as decoding from 'lib0/decoding' - -export class DeleteItem { - /** - * @param {number} clock - * @param {number} len - */ - constructor (clock, len) { - /** - * @type {number} - */ - this.clock = clock - /** - * @type {number} - */ - this.len = len - } -} - -/** - * We no longer maintain a DeleteStore. DeleteSet is a temporary object that is created when needed. - * - When created in a transaction, it must only be accessed after sorting, and merging - * - This DeleteSet is send to other clients - * - We do not create a DeleteSet when we send a sync message. The DeleteSet message is created directly from StructStore - * - We read a DeleteSet as part of a sync/update message. In this case the DeleteSet is already sorted and merged. - */ -export class DeleteSet { - constructor () { - /** - * @type {Map>} - */ - this.clients = new Map() - } -} - -/** - * Iterate over all structs that the DeleteSet gc's. - * - * @param {Transaction} transaction - * @param {DeleteSet} ds - * @param {function(GC|Item):void} f - * - * @function - */ -export const iterateDeletedStructs = (transaction, ds, f) => - ds.clients.forEach((deletes, clientid) => { - const structs = /** @type {Array} */ (transaction.doc.store.clients.get(clientid)) - for (let i = 0; i < deletes.length; i++) { - const del = deletes[i] - iterateStructs(transaction, structs, del.clock, del.len, f) - } - }) - -/** - * @param {Array} dis - * @param {number} clock - * @return {number|null} - * - * @private - * @function - */ -export const findIndexDS = (dis, clock) => { - let left = 0 - let right = dis.length - 1 - while (left <= right) { - const midindex = math.floor((left + right) / 2) - const mid = dis[midindex] - const midclock = mid.clock - if (midclock <= clock) { - if (clock < midclock + mid.len) { - return midindex - } - left = midindex + 1 - } else { - right = midindex - 1 - } - } - return null -} - -/** - * @param {DeleteSet} ds - * @param {ID} id - * @return {boolean} - * - * @private - * @function - */ -export const isDeleted = (ds, id) => { - const dis = ds.clients.get(id.client) - return dis !== undefined && findIndexDS(dis, id.clock) !== null -} - -/** - * @param {DeleteSet} ds - * - * @private - * @function - */ -export const sortAndMergeDeleteSet = ds => { - ds.clients.forEach(dels => { - dels.sort((a, b) => a.clock - b.clock) - // merge items without filtering or splicing the array - // i is the current pointer - // j refers to the current insert position for the pointed item - // try to merge dels[i] into dels[j-1] or set dels[j]=dels[i] - let i, j - for (i = 1, j = 1; i < dels.length; i++) { - const left = dels[j - 1] - const right = dels[i] - if (left.clock + left.len >= right.clock) { - left.len = math.max(left.len, right.clock + right.len - left.clock) - } else { - if (j < i) { - dels[j] = right - } - j++ - } - } - dels.length = j - }) -} - -/** - * @param {Array} dss - * @return {DeleteSet} A fresh DeleteSet - */ -export const mergeDeleteSets = dss => { - const merged = new DeleteSet() - for (let dssI = 0; dssI < dss.length; dssI++) { - dss[dssI].clients.forEach((delsLeft, client) => { - if (!merged.clients.has(client)) { - // Write all missing keys from current ds and all following. - // If merged already contains `client` current ds has already been added. - /** - * @type {Array} - */ - const dels = delsLeft.slice() - for (let i = dssI + 1; i < dss.length; i++) { - array.appendTo(dels, dss[i].clients.get(client) || []) - } - merged.clients.set(client, dels) - } - }) - } - sortAndMergeDeleteSet(merged) - return merged -} - -/** - * @param {DeleteSet} ds - * @param {number} client - * @param {number} clock - * @param {number} length - * - * @private - * @function - */ -export const addToDeleteSet = (ds, client, clock, length) => { - map.setIfUndefined(ds.clients, client, () => []).push(new DeleteItem(clock, length)) -} - -export const createDeleteSet = () => new DeleteSet() - -/** - * @param {StructStore} ss - * @return {DeleteSet} Merged and sorted DeleteSet - * - * @private - * @function - */ -export const createDeleteSetFromStructStore = ss => { - const ds = createDeleteSet() - ss.clients.forEach((structs, client) => { - /** - * @type {Array} - */ - const dsitems = [] - for (let i = 0; i < structs.length; i++) { - const struct = structs[i] - if (struct.deleted) { - const clock = struct.id.clock - let len = struct.length - if (i + 1 < structs.length) { - for (let next = structs[i + 1]; i + 1 < structs.length && next.deleted; next = structs[++i + 1]) { - len += next.length - } - } - dsitems.push(new DeleteItem(clock, len)) - } - } - if (dsitems.length > 0) { - ds.clients.set(client, dsitems) - } - }) - return ds -} - -/** - * @param {DSEncoderV1 | DSEncoderV2} encoder - * @param {DeleteSet} ds - * - * @private - * @function - */ -export const writeDeleteSet = (encoder, ds) => { - encoding.writeVarUint(encoder.restEncoder, ds.clients.size) - ds.clients.forEach((dsitems, client) => { - encoder.resetDsCurVal() - encoding.writeVarUint(encoder.restEncoder, client) - const len = dsitems.length - encoding.writeVarUint(encoder.restEncoder, len) - for (let i = 0; i < len; i++) { - const item = dsitems[i] - encoder.writeDsClock(item.clock) - encoder.writeDsLen(item.len) - } - }) -} - -/** - * @param {DSDecoderV1 | DSDecoderV2} decoder - * @return {DeleteSet} - * - * @private - * @function - */ -export const readDeleteSet = decoder => { - const ds = new DeleteSet() - const numClients = decoding.readVarUint(decoder.restDecoder) - for (let i = 0; i < numClients; i++) { - decoder.resetDsCurVal() - const client = decoding.readVarUint(decoder.restDecoder) - const numberOfDeletes = decoding.readVarUint(decoder.restDecoder) - if (numberOfDeletes > 0) { - const dsField = map.setIfUndefined(ds.clients, client, () => []) - for (let i = 0; i < numberOfDeletes; i++) { - dsField.push(new DeleteItem(decoder.readDsClock(), decoder.readDsLen())) - } - } - } - return ds -} - -/** - * @todo YDecoder also contains references to String and other Decoders. Would make sense to exchange YDecoder.toUint8Array for YDecoder.DsToUint8Array().. - */ - -/** - * @param {DSDecoderV1 | DSDecoderV2} decoder - * @param {Transaction} transaction - * @param {StructStore} store - * @return {Uint8Array|null} Returns a v2 update containing all deletes that couldn't be applied yet; or null if all deletes were applied successfully. - * - * @private - * @function - */ -export const readAndApplyDeleteSet = (decoder, transaction, store) => { - const unappliedDS = new DeleteSet() - const numClients = decoding.readVarUint(decoder.restDecoder) - for (let i = 0; i < numClients; i++) { - decoder.resetDsCurVal() - const client = decoding.readVarUint(decoder.restDecoder) - const numberOfDeletes = decoding.readVarUint(decoder.restDecoder) - const structs = store.clients.get(client) || [] - const state = getState(store, client) - for (let i = 0; i < numberOfDeletes; i++) { - const clock = decoder.readDsClock() - const clockEnd = clock + decoder.readDsLen() - if (clock < state) { - if (state < clockEnd) { - addToDeleteSet(unappliedDS, client, state, clockEnd - state) - } - let index = findIndexSS(structs, clock) - /** - * We can ignore the case of GC and Delete structs, because we are going to skip them - * @type {Item} - */ - // @ts-ignore - let struct = structs[index] - // split the first item if necessary - if (!struct.deleted && struct.id.clock < clock) { - structs.splice(index + 1, 0, splitItem(transaction, struct, clock - struct.id.clock)) - index++ // increase we now want to use the next struct - } - while (index < structs.length) { - // @ts-ignore - struct = structs[index++] - if (struct.id.clock < clockEnd) { - if (!struct.deleted) { - if (clockEnd < struct.id.clock + struct.length) { - structs.splice(index, 0, splitItem(transaction, struct, clockEnd - struct.id.clock)) - } - struct.delete(transaction) - } - } else { - break - } - } - } else { - addToDeleteSet(unappliedDS, client, clock, clockEnd - clock) - } - } - } - if (unappliedDS.clients.size > 0) { - const ds = new UpdateEncoderV2() - encoding.writeVarUint(ds.restEncoder, 0) // encode 0 structs - writeDeleteSet(ds, unappliedDS) - return ds.toUint8Array() - } - return null -} diff --git a/src/utils/Doc.js b/src/utils/Doc.js index ace76392b..58590d34b 100644 --- a/src/utils/Doc.js +++ b/src/utils/Doc.js @@ -8,12 +8,15 @@ import { YArray, YText, YMap, + YXmlElement, YXmlFragment, transact, - ContentDoc, Item, Transaction, YEvent // eslint-disable-line + applyUpdate, + ContentDoc, Item, Transaction, // eslint-disable-line + encodeStateAsUpdate } from '../internals.js' -import { Observable } from 'lib0/observable' +import { ObservableV2 } from 'lib0/observable' import * as random from 'lib0/random' import * as map from 'lib0/map' import * as array from 'lib0/array' @@ -21,6 +24,13 @@ import * as promise from 'lib0/promise' export const generateNewClientId = random.uint32 +/** + * @typedef {import('../utils/types.js').YTypeConstructors} YTypeConstructors + */ +/** + * @typedef {import('../utils/types.js').YType} YType + */ + /** * @typedef {Object} DocOpts * @property {boolean} [DocOpts.gc=true] Disable garbage collection (default: gc=true) @@ -30,25 +40,46 @@ export const generateNewClientId = random.uint32 * @property {any} [DocOpts.meta] Any kind of meta information you want to associate with this document. If this is a subdocument, remote peers will store the meta information as well. * @property {boolean} [DocOpts.autoLoad] If a subdocument, automatically load document. If this is a subdocument, remote peers will load the document as well automatically. * @property {boolean} [DocOpts.shouldLoad] Whether the document should be synced by the provider now. This is toggled to true when you call ydoc.load() + * @property {boolean} [DocOpts.isSuggestionDoc] Set to true if this document merely suggests + * changes. If this flag is not set in a suggestion document, automatic formatting changes will be + * displayed as suggestions, which might not be intended. + */ + +/** + * @typedef {Object} DocEvents + * @property {function(Doc):void} DocEvents.destroy + * @property {function(Doc):void} DocEvents.load + * @property {function(boolean, Doc):void} DocEvents.sync + * @property {function(Uint8Array, any, Doc, Transaction):void} DocEvents.update + * @property {function(Uint8Array, any, Doc, Transaction):void} DocEvents.updateV2 + * @property {function(Doc):void} DocEvents.beforeAllTransactions + * @property {function(Transaction, Doc):void} DocEvents.beforeTransaction + * @property {function(Transaction, Doc):void} DocEvents.beforeObserverCalls + * @property {function(Transaction, Doc):void} DocEvents.afterTransaction + * @property {function(Transaction, Doc):void} DocEvents.afterTransactionCleanup + * @property {function(Doc, Array):void} DocEvents.afterAllTransactions + * @property {function({ loaded: Set, added: Set, removed: Set }, Doc, Transaction):void} DocEvents.subdocs */ /** * A Yjs instance handles the state of shared data. - * @extends Observable + * @extends ObservableV2 */ -export class Doc extends Observable { +export class Doc extends ObservableV2 { /** - * @param {DocOpts} [opts] configuration + * @param {DocOpts} opts configuration */ - constructor ({ guid = random.uuidv4(), collectionid = null, gc = true, gcFilter = () => true, meta = null, autoLoad = false, shouldLoad = true } = {}) { + constructor ({ guid = random.uuidv4(), collectionid = null, gc = true, gcFilter = () => true, meta = null, autoLoad = false, shouldLoad = true, isSuggestionDoc = false } = {}) { super() this.gc = gc this.gcFilter = gcFilter this.clientID = generateNewClientId() this.guid = guid this.collectionid = collectionid + this.isSuggestionDoc = isSuggestionDoc + this.cleanupFormatting = !isSuggestionDoc /** - * @type {Map>>} + * @type {Map} */ this.share = new Map() this.store = new StructStore() @@ -72,13 +103,58 @@ export class Doc extends Observable { this.shouldLoad = shouldLoad this.autoLoad = autoLoad this.meta = meta + /** + * This is set to true when the persistence provider loaded the document from the database or when the `sync` event fires. + * Note that not all providers implement this feature. Provider authors are encouraged to fire the `load` event when the doc content is loaded from the database. + * + * @type {boolean} + */ this.isLoaded = false + /** + * This is set to true when the connection provider has successfully synced with a backend. + * Note that when using peer-to-peer providers this event may not provide very useful. + * Also note that not all providers implement this feature. Provider authors are encouraged to fire + * the `sync` event when the doc has been synced (with `true` as a parameter) or if connection is + * lost (with false as a parameter). + */ + this.isSynced = false + this.isDestroyed = false + /** + * Promise that resolves once the document has been loaded from a persistence provider. + */ this.whenLoaded = promise.create(resolve => { this.on('load', () => { this.isLoaded = true resolve(this) }) }) + const provideSyncedPromise = () => promise.create(resolve => { + /** + * @param {boolean} isSynced + */ + const eventHandler = (isSynced) => { + if (isSynced === undefined || isSynced === true) { + this.off('sync', eventHandler) + resolve() + } + } + this.on('sync', eventHandler) + }) + this.on('sync', isSynced => { + if (isSynced === false && this.isSynced) { + this.whenSynced = provideSyncedPromise() + } + this.isSynced = isSynced === undefined || isSynced === true + if (this.isSynced && !this.isLoaded) { + this.emit('load', [this]) + } + }) + /** + * Promise that resolves once the document has been synced with a backend. + * This promise is recreated when the connection is lost. + * Note the documentation about the `isSynced` property. + */ + this.whenSynced = provideSyncedPromise() } /** @@ -103,7 +179,7 @@ export class Doc extends Observable { } getSubdocGuids () { - return new Set(Array.from(this.subdocs).map(doc => doc.guid)) + return new Set(array.from(this.subdocs).map(doc => doc.guid)) } /** @@ -112,42 +188,45 @@ export class Doc extends Observable { * that happened inside of the transaction are sent as one message to the * other peers. * - * @param {function(Transaction):void} f The function that should be executed as a transaction + * @template T + * @param {function(Transaction):T} f The function that should be executed as a transaction * @param {any} [origin] Origin of who started the transaction. Will be stored on transaction.origin + * @return T * * @public */ transact (f, origin = null) { - transact(this, f, origin) + return transact(this, f, origin) } /** * Define a shared data type. * - * Multiple calls of `y.get(name, TypeConstructor)` yield the same result + * Multiple calls of `ydoc.get(name, TypeConstructor)` yield the same result * and do not overwrite each other. I.e. - * `y.define(name, Y.Array) === y.define(name, Y.Array)` + * `ydoc.get(name, Y.Array) === ydoc.get(name, Y.Array)` * - * After this method is called, the type is also available on `y.share.get(name)`. + * After this method is called, the type is also available on `ydoc.share.get(name)`. * * *Best Practices:* - * Define all types right after the Yjs instance is created and store them in a separate object. + * Define all types right after the Y.Doc instance is created and store them in a separate object. * Also use the typed methods `getText(name)`, `getArray(name)`, .. * + * @template {YTypeConstructors} TypeC * @example - * const y = new Y(..) + * const ydoc = new Y.Doc(..) * const appState = { - * document: y.getText('document') - * comments: y.getArray('comments') + * document: ydoc.getText('document') + * comments: ydoc.getArray('comments') * } * * @param {string} name - * @param {Function} TypeConstructor The constructor of the type definition. E.g. Y.Text, Y.Array, Y.Map, ... - * @return {AbstractType} The created type. Constructed with TypeConstructor + * @param {TypeC} TypeConstructor The constructor of the type definition. E.g. Y.Text, Y.Array, Y.Map, ... + * @return {InstanceType} The created type. Constructed with TypeConstructor * * @public */ - get (name, TypeConstructor = AbstractType) { + get (name, TypeConstructor = /** @type {any} */ (AbstractType)) { const type = map.setIfUndefined(this.share, name, () => { // @ts-ignore const t = new TypeConstructor() @@ -155,6 +234,7 @@ export class Doc extends Observable { return t }) const Constr = type.constructor + // @ts-ignore if (TypeConstructor !== AbstractType && Constr !== TypeConstructor) { if (Constr === AbstractType) { // @ts-ignore @@ -173,12 +253,12 @@ export class Doc extends Observable { t._length = type._length this.share.set(name, t) t._integrate(this, null) - return t + return /** @type {InstanceType} */ (t) } else { throw new Error(`Type with the name ${name} has already been defined with a different constructor`) } } - return type + return /** @type {InstanceType} */ (type) } /** @@ -189,8 +269,7 @@ export class Doc extends Observable { * @public */ getArray (name = '') { - // @ts-ignore - return this.get(name, YArray) + return /** @type {YArray} */ (this.get(name, YArray)) } /** @@ -200,7 +279,6 @@ export class Doc extends Observable { * @public */ getText (name = '') { - // @ts-ignore return this.get(name, YText) } @@ -212,8 +290,17 @@ export class Doc extends Observable { * @public */ getMap (name = '') { - // @ts-ignore - return this.get(name, YMap) + return /** @type {YMap} */ (this.get(name, YMap)) + } + + /** + * @param {string} [name] + * @return {YXmlElement} + * + * @public + */ + getXmlElement (name = '') { + return /** @type {YXmlElement<{[key:string]:string}>} */ (this.get(name, YXmlElement)) } /** @@ -223,7 +310,6 @@ export class Doc extends Observable { * @public */ getXmlFragment (name = '') { - // @ts-ignore return this.get(name, YXmlFragment) } @@ -252,6 +338,7 @@ export class Doc extends Observable { * Emit `destroy` event and unregister all event handlers. */ destroy () { + this.isDestroyed = true array.from(this.subdocs).forEach(subdoc => subdoc.destroy()) const item = this._item if (item !== null) { @@ -267,24 +354,19 @@ export class Doc extends Observable { transaction.subdocsRemoved.add(this) }, null, true) } - this.emit('destroyed', [true]) + // @ts-ignore + this.emit('destroyed', [true]) // DEPRECATED! this.emit('destroy', [this]) super.destroy() } +} - /** - * @param {string} eventName - * @param {function(...any):any} f - */ - on (eventName, f) { - super.on(eventName, f) - } - - /** - * @param {string} eventName - * @param {function} f - */ - off (eventName, f) { - super.off(eventName, f) - } +/** + * @param {Doc} ydoc + * @param {DocOpts} [opts] + */ +export const cloneDoc = (ydoc, opts) => { + const clone = new Doc(opts) + applyUpdate(clone, encodeStateAsUpdate(ydoc)) + return clone } diff --git a/src/utils/ID.js b/src/utils/ID.js index 225ee5b4c..b0cabd8f3 100644 --- a/src/utils/ID.js +++ b/src/utils/ID.js @@ -1,4 +1,3 @@ - import { AbstractType } from '../internals.js' // eslint-disable-line import * as decoding from 'lib0/decoding' diff --git a/src/utils/IdMap.js b/src/utils/IdMap.js new file mode 100644 index 000000000..e9fff56b7 --- /dev/null +++ b/src/utils/IdMap.js @@ -0,0 +1,627 @@ +import { + _diffSet, + findIndexInIdRanges, + findRangeStartInIdRanges, + _deleteRangeFromIdSet, + DSDecoderV1, DSDecoderV2, IdSetEncoderV1, IdSetEncoderV2, IdSet, ID, // eslint-disable-line + _insertIntoIdSet, + _intersectSets +} from '../internals.js' + +import * as array from 'lib0/array' +import * as map from 'lib0/map' +import * as encoding from 'lib0/encoding' +import * as decoding from 'lib0/decoding' +import * as buf from 'lib0/buffer' +import * as rabin from 'lib0/hash/rabin' + +/** + * @template V + */ +export class AttributionItem { + /** + * @param {string} name + * @param {V} val + */ + constructor (name, val) { + this.name = name + this.val = val + } + + hash () { + const encoder = encoding.createEncoder() + encoding.writeVarString(encoder, this.name) + encoding.writeAny(encoder, /** @type {any} */ (this.val)) + return buf.toBase64(rabin.fingerprint(rabin.StandardIrreducible128, encoding.toUint8Array(encoder))) + } +} + +/** + * @param {AttributionItem} attr + */ +const _hashAttribution = attr => { + const encoder = encoding.createEncoder() + encoding.writeVarString(encoder, attr.name) + encoding.writeAny(encoder, attr.val) + return buf.toBase64(rabin.fingerprint(rabin.StandardIrreducible128, encoding.toUint8Array(encoder))) +} + +/** + * @template V + * @param {string} name + * @param {V} val + * @return {AttributionItem} + */ +export const createAttributionItem = (name, val) => new AttributionItem(name, val) + +/** + * @template T + * @param {Array} attrs + * @param {T} attr + * + */ +const idmapAttrsHas = (attrs, attr) => attrs.find(a => a === attr) + +/** + * @template T + * @param {Array} a + * @param {Array} b + */ +export const idmapAttrsEqual = (a, b) => a.length === b.length && a.every(v => idmapAttrsHas(b, v)) + +/** + * @template T + * @param {Array} a + * @param {Array} b + */ +const idmapAttrRangeJoin = (a, b) => a.concat(b.filter(attr => !idmapAttrsHas(a, attr))) + +/** + * @template Attrs + */ +export class AttrRange { + /** + * @param {number} clock + * @param {number} len + * @param {Array>} attrs + */ + constructor (clock, len, attrs) { + /** + * @readonly + */ + this.clock = clock + /** + * @readonly + */ + this.len = len + /** + * @readonly + */ + this.attrs = attrs + } + + /** + * @param {number} clock + * @param {number} len + */ + copyWith (clock, len) { + return new AttrRange(clock, len, this.attrs) + } +} + +/** + * @template Attrs + * @typedef {{ clock: number, len: number, attrs: Array>? }} MaybeAttrRange + */ + +/** + * @template Attrs + * + * @param {number} clock + * @param {number} len + * @param {Array>?} attrs + * @return {MaybeAttrRange} + */ +export const createMaybeAttrRange = (clock, len, attrs) => new AttrRange(clock, len, /** @type {any} */ (attrs)) + +/** + * Whenever this is instantiated, it must receive a fresh array of ops, not something copied. + * + * @template Attrs + */ +export class AttrRanges { + /** + * @param {Array>} ids + */ + constructor (ids) { + this.sorted = false + /** + * @private + */ + this._ids = ids + } + + copy () { + return new AttrRanges(this._ids.slice()) + } + + /** + * @param {number} clock + * @param {number} length + * @param {Array>} attrs + */ + add (clock, length, attrs) { + if (length === 0) return + this.sorted = false + this._ids.push(new AttrRange(clock, length, attrs)) + } + + /** + * Return the list of id ranges, sorted and merged. + */ + getIds () { + const ids = this._ids + if (!this.sorted) { + this.sorted = true + ids.sort((a, b) => a.clock - b.clock) + /** + * algorithm thoughts: + * - sort (by clock AND by length), bigger length is to the right (or not, we can't make + * assumptions abouth length after long length has been split) + * -- maybe better: sort by clock+length. Then split items from right to left. This way, items are always + * in the right order. But I also need to swap if left items is smaller after split + * --- thought: there is no way to go around swapping. Unless, for each item from left to + * right, when I have to split because one of the look-ahead items is overlapping, i split + * it and merge the attributes into the following ones (that I also need to split). Best is + * probably left to right with lookahead. + * - left to right, split overlapping items so that we can make the assumption that either an + * item is overlapping with the next 1-on-1 or it is not overlapping at all (when splitting, + * we can already incorporate the attributes) + * -- better: for each item, go left to right and add own attributes to overlapping items. + * Split them if necessary. After split, i must insert the retainer at a valid position. + * - merge items if neighbor has same attributes + */ + for (let i = 0; i < ids.length - 1;) { + const range = ids[i] + const nextRange = ids[i + 1] + // find out how to split range. it must match with next range. + // 1) we have space. Split if necessary. + // 2) concat attributes in range to the next range. Split range and splice the remainder at + // the correct position. + if (range.clock < nextRange.clock) { // might need to split range + if (range.clock + range.len > nextRange.clock) { + // is overlapping + const diff = nextRange.clock - range.clock + ids[i] = new AttrRange(range.clock, diff, range.attrs) + ids.splice(i + 1, 0, new AttrRange(nextRange.clock, range.len - diff, range.attrs)) + } + i++ + continue + } + // now we know that range.clock === nextRange.clock + // merge range with nextRange + const largerRange = range.len > nextRange.len ? range : nextRange + const smallerLen = range.len < nextRange.len ? range.len : nextRange.len + ids[i] = new AttrRange(range.clock, smallerLen, idmapAttrRangeJoin(range.attrs, nextRange.attrs)) + if (range.len === nextRange.len) { + ids.splice(i + 1, 1) + } else { + ids[i + 1] = new AttrRange(range.clock + smallerLen, largerRange.len - smallerLen, largerRange.attrs) + array.bubblesortItem(ids, i + 1, (a, b) => a.clock - b.clock) + } + if (smallerLen === 0) i++ + } + while (ids.length > 0 && ids[0].len === 0) { + ids.splice(0, 1) + } + // merge items without filtering or splicing the array. + // i is the current pointer + // j refers to the current insert position for the pointed item + // try to merge dels[i] into dels[j-1] or set dels[j]=dels[i] + let i, j + for (i = 1, j = 1; i < ids.length; i++) { + const left = ids[j - 1] + const right = ids[i] + if (left.clock + left.len === right.clock && idmapAttrsEqual(left.attrs, right.attrs)) { + ids[j - 1] = new AttrRange(left.clock, left.len + right.len, left.attrs) + } else if (right.len !== 0) { + if (j < i) { + ids[j] = right + } + j++ + } + } + ids.length = ids.length === 0 ? 0 : (ids[j - 1].len === 0 ? j - 1 : j) + } + return ids + } +} + +/** + * Merge multiple idmaps. Ensures that there are no redundant attribution definitions (two + * Attributions that describe the same thing). + * + * @template T + * @param {Array>} ams + * @return {IdMap} A fresh IdSet + */ +export const mergeIdMaps = ams => { + /** + * Maps attribution to the attribution of the merged idmap. + * + * @type {Map,AttributionItem>} + */ + const attrMapper = new Map() + const merged = createIdMap() + for (let amsI = 0; amsI < ams.length; amsI++) { + ams[amsI].clients.forEach((rangesLeft, client) => { + if (!merged.clients.has(client)) { + // Write all missing keys from current set and all following. + // If merged already contains `client` current ds has already been added. + let ids = rangesLeft.getIds().slice() + for (let i = amsI + 1; i < ams.length; i++) { + const nextIds = ams[i].clients.get(client) + if (nextIds) { + array.appendTo(ids, nextIds.getIds()) + } + } + ids = ids.map(id => new AttrRange(id.clock, id.len, id.attrs.map(attr => + map.setIfUndefined(attrMapper, attr, () => + _ensureAttrs(merged, [attr])[0] + ) + ))) + merged.clients.set(client, new AttrRanges(ids)) + } + }) + } + return merged +} + +/** + * @param {IdSet} idset + * @param {Array>} attrs + */ +export const createIdMapFromIdSet = (idset, attrs) => { + const idmap = createIdMap() + // map attrs to idmap + attrs = _ensureAttrs(idmap, attrs) + // filter out duplicates + /** + * @type {Array>} + */ + const checkedAttrs = [] + attrs.forEach(attr => { + if (!idmapAttrsHas(checkedAttrs, attr)) { + checkedAttrs.push(attr) + } + }) + idset.clients.forEach((ranges, client) => { + const attrRanges = new AttrRanges(ranges.getIds().map(range => new AttrRange(range.clock, range.len, checkedAttrs))) + attrRanges.sorted = true // is sorted because idset is sorted + idmap.clients.set(client, attrRanges) + }) + return idmap +} + +/** + * @template Attrs + */ +export class IdMap { + constructor () { + /** + * @type {Map>} + */ + this.clients = new Map() + /** + * @type {Map>} + */ + this.attrsH = new Map() + /** + * @type {Set>} + */ + this.attrs = new Set() + } + + /** + * @param {(attrRange:AttrRange, client:number) => void} f + */ + forEach (f) { + this.clients.forEach((ranges, client) => { + ranges.getIds().forEach((range) => { + f(range, client) + }) + }) + } + + /** + * @param {ID} id + * @return {boolean} + */ + hasId (id) { + return this.has(id.client, id.clock) + } + + /** + * @param {number} client + * @param {number} clock + * @return {boolean} + */ + has (client, clock) { + const dr = this.clients.get(client) + if (dr) { + return findIndexInIdRanges(dr.getIds(), clock) !== null + } + return false + } + + /** + * Return attributions for a slice of ids. + * + * @param {ID} id + * @param {number} len + * @return {Array>} + */ + sliceId (id, len) { + return this.slice(id.client, id.clock, len) + } + + /** + * Return attributions for a slice of ids. + * + * @param {number} client + * @param {number} clock + * @param {number} len + * @return {Array>} + */ + slice (client, clock, len) { + const dr = this.clients.get(client) + /** + * @type {Array>} + */ + const res = [] + if (dr) { + /** + * @type {Array>} + */ + const ranges = dr.getIds() + let index = findRangeStartInIdRanges(ranges, clock) + if (index !== null) { + let prev = null + while (index < ranges.length) { + let r = ranges[index] + if (r.clock < clock) { + r = new AttrRange(clock, r.len - (clock - r.clock), r.attrs) + } + if (r.clock + r.len > clock + len) { + r = new AttrRange(r.clock, clock + len - r.clock, r.attrs) + } + if (r.len <= 0) break + const prevEnd = prev != null ? prev.clock + prev.len : clock + if (prevEnd < r.clock) { + res.push(createMaybeAttrRange(prevEnd, r.clock - prevEnd, null)) + } + prev = r + res.push(r) + index++ + } + } + } + if (res.length > 0) { + const last = res[res.length - 1] + const end = last.clock + last.len + if (end < clock + len) { + res.push(createMaybeAttrRange(end, clock + len - end, null)) + } + } else { + res.push(createMaybeAttrRange(clock, len, null)) + } + return res + } + + /** + * @param {number} client + * @param {number} clock + * @param {number} len + * @param {Array>} attrs + */ + add (client, clock, len, attrs) { + if (len === 0) return + attrs = _ensureAttrs(this, attrs) + const ranges = this.clients.get(client) + if (ranges == null) { + this.clients.set(client, new AttrRanges([new AttrRange(clock, len, attrs)])) + } else { + ranges.add(clock, len, attrs) + } + } + + /** + * @param {number} client + * @param {number} clock + * @param {number} len + */ + delete (client, clock, len) { + _deleteRangeFromIdSet(this, client, clock, len) + } +} + +/** + * Efficiently encodes IdMap to a binary form. Ensures that information is de-duplicated when + * written. Attribute.names are referenced by id. Attributes themselfs are also referenced by id. + * + * @template Attr + * @param {IdSetEncoderV1 | IdSetEncoderV2} encoder + * @param {IdMap} idmap + * + * @private + * @function + */ +export const writeIdMap = (encoder, idmap) => { + encoding.writeVarUint(encoder.restEncoder, idmap.clients.size) + let lastWrittenClientId = 0 + /** + * @type {Map, number>} + */ + const visitedAttributions = map.create() + /** + * @type {Map} + */ + const visitedAttrNames = map.create() + // Ensure that the delete set is written in a deterministic order (smaller clientids first) + array.from(idmap.clients.entries()) + .sort((a, b) => a[0] - b[0]) + .forEach(([client, _idRanges]) => { + const attrRanges = _idRanges.getIds() + encoder.resetIdSetCurVal() + const diff = client - lastWrittenClientId + encoding.writeVarUint(encoder.restEncoder, diff) + lastWrittenClientId = client + const len = attrRanges.length + encoding.writeVarUint(encoder.restEncoder, len) + for (let i = 0; i < len; i++) { + const item = attrRanges[i] + const attrs = item.attrs + const attrLen = attrs.length + encoder.writeIdSetClock(item.clock) + encoder.writeIdSetLen(item.len) + encoding.writeVarUint(encoder.restEncoder, attrLen) + for (let j = 0; j < attrLen; j++) { + const attr = attrs[j] + const attrId = visitedAttributions.get(attr) + if (attrId != null) { + encoding.writeVarUint(encoder.restEncoder, attrId) + } else { + const newAttrId = visitedAttributions.size + visitedAttributions.set(attr, newAttrId) + encoding.writeVarUint(encoder.restEncoder, newAttrId) + const attrNameId = visitedAttrNames.get(attr.name) + // write attr.name + if (attrNameId != null) { + encoding.writeVarUint(encoder.restEncoder, attrNameId) + } else { + const newAttrNameId = visitedAttrNames.size + encoding.writeVarUint(encoder.restEncoder, newAttrNameId) + encoding.writeVarString(encoder.restEncoder, attr.name) + visitedAttrNames.set(attr.name, newAttrNameId) + } + encoding.writeAny(encoder.restEncoder, /** @type {any} */ (attr.val)) + } + } + } + }) +} + +/** + * @param {IdMap} idmap + */ +export const encodeIdMap = idmap => { + const encoder = new IdSetEncoderV2() + writeIdMap(encoder, idmap) + return encoder.toUint8Array() +} + +/** + * @param {DSDecoderV1 | DSDecoderV2} decoder + * @return {IdMap} + * + * @private + * @function + */ +export const readIdMap = decoder => { + const idmap = new IdMap() + const numClients = decoding.readVarUint(decoder.restDecoder) + /** + * @type {Array>} + */ + const visitedAttributions = [] + /** + * @type {Array} + */ + const visitedAttrNames = [] + let lastClientId = 0 + for (let i = 0; i < numClients; i++) { + decoder.resetDsCurVal() + const client = lastClientId + decoding.readVarUint(decoder.restDecoder) + lastClientId = client + const numberOfDeletes = decoding.readVarUint(decoder.restDecoder) + /** + * @type {Array>} + */ + const attrRanges = [] + for (let i = 0; i < numberOfDeletes; i++) { + const rangeClock = decoder.readDsClock() + const rangeLen = decoder.readDsLen() + /** + * @type {Array>} + */ + const attrs = [] + const attrsLen = decoding.readVarUint(decoder.restDecoder) + for (let j = 0; j < attrsLen; j++) { + const attrId = decoding.readVarUint(decoder.restDecoder) + if (attrId >= visitedAttributions.length) { + // attrId not known yet + const attrNameId = decoding.readVarUint(decoder.restDecoder) + if (attrNameId >= visitedAttrNames.length) { + visitedAttrNames.push(decoding.readVarString(decoder.restDecoder)) + } + visitedAttributions.push(new AttributionItem(visitedAttrNames[attrNameId], decoding.readAny(decoder.restDecoder))) + } + attrs.push(visitedAttributions[attrId]) + } + attrRanges.push(new AttrRange(rangeClock, rangeLen, attrs)) + } + idmap.clients.set(client, new AttrRanges(attrRanges)) + } + visitedAttributions.forEach(attr => { + idmap.attrs.add(attr) + idmap.attrsH.set(attr.hash(), attr) + }) + return idmap +} + +/** + * @param {Uint8Array} data + * @return {IdMap} + */ +export const decodeIdMap = data => readIdMap(new DSDecoderV2(decoding.createDecoder(data))) + +/** + * @template Attrs + * @param {IdMap} idmap + * @param {Array>} attrs + * @return {Array>} + */ +const _ensureAttrs = (idmap, attrs) => attrs.map(attr => + idmap.attrs.has(attr) + ? attr + : map.setIfUndefined(idmap.attrsH, _hashAttribution(attr), () => { + idmap.attrs.add(attr) + return attr + })) + +export const createIdMap = () => new IdMap() + +/** + * @template T + * @param {IdMap} dest + * @param {IdMap} src + */ +export const insertIntoIdMap = _insertIntoIdSet + +/** + * Remove all ranges from `exclude` from `ds`. The result is a fresh IdMap containing all ranges from `idSet` that are not + * in `exclude`. + * + * @template {IdMap} ISet + * @param {ISet} set + * @param {IdSet | IdMap} exclude + * @return {ISet} + */ +export const diffIdMap = (set, exclude) => { + const diffed = _diffSet(set, exclude) + diffed.attrs = set.attrs + diffed.attrsH = set.attrsH + return diffed +} + +export const intersectMaps = _intersectSets diff --git a/src/utils/IdSet.js b/src/utils/IdSet.js new file mode 100644 index 000000000..8b3a20deb --- /dev/null +++ b/src/utils/IdSet.js @@ -0,0 +1,812 @@ +import { + findIndexSS, + getState, + splitItem, + iterateStructs, + UpdateEncoderV2, + IdMap, + AttrRanges, + AttrRange, + Skip, AbstractStruct, DSDecoderV1, IdSetEncoderV1, DSDecoderV2, IdSetEncoderV2, Item, GC, StructStore, Transaction, ID, AttributionItem, // eslint-disable-line +} from '../internals.js' + +import * as array from 'lib0/array' +import * as math from 'lib0/math' +import * as encoding from 'lib0/encoding' +import * as decoding from 'lib0/decoding' + +export class IdRange { + /** + * @param {number} clock + * @param {number} len + */ + constructor (clock, len) { + /** + * @type {number} + */ + this.clock = clock + /** + * @type {number} + */ + this.len = len + } + + /** + * @param {number} clock + * @param {number} len + */ + copyWith (clock, len) { + return new IdRange(clock, len) + } + + /** + * Helper method making this compatible with IdMap. + * + * @return {Array>} + */ + get attrs () { + return [] + } +} + +export class MaybeIdRange { + /** + * @param {number} clock + * @param {number} len + * @param {boolean} exists + */ + constructor (clock, len, exists) { + /** + * @type {number} + */ + this.clock = clock + /** + * @type {number} + */ + this.len = len + /** + * @type {boolean} + */ + this.exists = exists + } +} + +/** + * @param {number} clock + * @param {number} len + * @param {boolean} exists + * @return {MaybeIdRange} + */ +export const createMaybeIdRange = (clock, len, exists) => new MaybeIdRange(clock, len, exists) + +export class IdRanges { + /** + * @param {Array} ids + */ + constructor (ids) { + this.sorted = false + /** + * A typical use-case for IdSet is to append data. We heavily optimize this case by allowing the + * last item to be mutated ef it isn't used currently. + * This flag is true if the last item was exposed to the outside. + */ + this._lastIsUsed = false + /** + * @private + */ + this._ids = ids + } + + copy () { + return new IdRanges(this._ids.slice()) + } + + /** + * @param {number} clock + * @param {number} length + */ + add (clock, length) { + const last = this._ids[this._ids.length - 1] + if (last.clock + last.len === clock) { + if (this._lastIsUsed) { + this._ids[this._ids.length - 1] = new IdRange(last.clock, last.len + length) + this._lastIsUsed = false + } else { + this._ids[this._ids.length - 1].len += length + } + } else { + this.sorted = false + this._ids.push(new IdRange(clock, length)) + } + } + + /** + * Return the list of immutable id ranges, sorted and merged. + */ + getIds () { + const ids = this._ids + this._lastIsUsed = true + if (!this.sorted) { + this.sorted = true + ids.sort((a, b) => a.clock - b.clock) + // merge items without filtering or splicing the array + // i is the current pointer + // j refers to the current insert position for the pointed item + // try to merge dels[i] into dels[j-1] or set dels[j]=dels[i] + let i, j + for (i = 1, j = 1; i < ids.length; i++) { + const left = ids[j - 1] + const right = ids[i] + if (left.clock + left.len >= right.clock) { + const r = right.clock + right.len - left.clock + if (left.len < r) { + ids[j - 1] = new IdRange(left.clock, r) + } + } else if (left.len === 0) { + ids[j - 1] = right + } else { + if (j < i) { + ids[j] = right + } + j++ + } + } + ids.length = ids[j - 1].len === 0 ? j - 1 : j + } + return ids + } +} + +export class IdSet { + constructor () { + /** + * @type {Map} + */ + this.clients = new Map() + } + + isEmpty () { + return this.clients.size === 0 + } + + /** + * @param {(idrange:IdRange, client:number) => void} f + */ + forEach (f) { + this.clients.forEach((ranges, client) => { + ranges.getIds().forEach((range) => { + f(range, client) + }) + }) + } + + /** + * @param {ID} id + * @return {boolean} + */ + hasId (id) { + return this.has(id.client, id.clock) + } + + /** + * @param {number} client + * @param {number} clock + */ + has (client, clock) { + const dr = this.clients.get(client) + if (dr) { + return findIndexInIdRanges(dr.getIds(), clock) !== null + } + return false + } + + /** + * Return slices of ids that exist in this idset. + * + * @param {number} client + * @param {number} clock + * @param {number} len + * @return {Array} + */ + slice (client, clock, len) { + const dr = this.clients.get(client) + /** + * @type {Array} + */ + const res = [] + if (dr) { + /** + * @type {Array} + */ + const ranges = dr.getIds() + let index = findRangeStartInIdRanges(ranges, clock) + if (index !== null) { + let prev = null + while (index < ranges.length) { + let r = ranges[index] + if (r.clock < clock) { + r = new IdRange(clock, r.len - (clock - r.clock)) + } + if (r.clock + r.len > clock + len) { + r = new IdRange(r.clock, clock + len - r.clock) + } + if (r.len <= 0) break + const prevEnd = prev != null ? prev.clock + prev.len : clock + if (prevEnd < r.clock) { + res.push(createMaybeIdRange(prevEnd, r.clock - prevEnd, false)) + } + prev = r + res.push(createMaybeIdRange(r.clock, r.len, true)) + index++ + } + } + } + if (res.length > 0) { + const last = res[res.length - 1] + const end = last.clock + last.len + if (end < clock + len) { + res.push(createMaybeIdRange(end, clock + len - end, false)) + } + } else { + res.push(createMaybeIdRange(clock, len, false)) + } + return res + } + + /** + * @param {number} client + * @param {number} clock + * @param {number} len + */ + add (client, clock, len) { + addToIdSet(this, client, clock, len) + } + + /** + * @param {number} client + * @param {number} clock + * @param {number} len + */ + delete (client, clock, len) { + _deleteRangeFromIdSet(this, client, clock, len) + } +} + +/** + * @param {IdSet | IdMap} set + * @param {number} client + * @param {number} clock + * @param {number} len + */ +export const _deleteRangeFromIdSet = (set, client, clock, len) => { + const dr = set.clients.get(client) + if (dr && len > 0) { + const ids = dr.getIds() + let index = findRangeStartInIdRanges(ids, clock) + if (index != null) { + for (let r = ids[index]; index < ids.length && r.clock < clock + len; r = ids[++index]) { + if (r.clock < clock) { + ids[index] = r.copyWith(r.clock, clock - r.clock) + if (clock + len < r.clock + r.len) { + ids.splice(index + 1, 0, r.copyWith(clock + len, r.clock + r.len - clock - len)) + } + } else if (clock + len < r.clock + r.len) { + // need to retain end + ids[index] = r.copyWith(clock + len, r.clock + r.len - clock - len) + } else if (ids.length === 1) { + set.clients.delete(client) + return + } else { + ids.splice(index--, 1) + } + } + } + } +} + +/** + * Iterate over all structs that are mentioned by the IdSet. + * + * @param {Transaction} transaction + * @param {IdSet} ds + * @param {function(GC|Item):void} f + * + * @function + */ +export const iterateStructsByIdSet = (transaction, ds, f) => + ds.clients.forEach((idRanges, clientid) => { + const ranges = idRanges.getIds() + const structs = /** @type {Array} */ (transaction.doc.store.clients.get(clientid)) + if (structs != null) { + for (let i = 0; i < ranges.length; i++) { + const del = ranges[i] + iterateStructs(transaction, structs, del.clock, del.len, f) + } + } + }) + +/** + * @param {Array} dis + * @param {number} clock + * @return {number|null} + * + * @private + * @function + */ +export const findIndexInIdRanges = (dis, clock) => { + let left = 0 + let right = dis.length - 1 + while (left <= right) { + const midindex = math.floor((left + right) / 2) + const mid = dis[midindex] + const midclock = mid.clock + if (midclock <= clock) { + if (clock < midclock + mid.len) { + return midindex + } + left = midindex + 1 + } else { + right = midindex - 1 + } + } + return null +} + +/** + * Find the first range that contains clock or comes after clock. + * + * @param {Array} dis + * @param {number} clock + * @return {number|null} + * + * @private + * @function + */ +export const findRangeStartInIdRanges = (dis, clock) => { + let left = 0 + let right = dis.length - 1 + while (left <= right) { + const midindex = math.floor((left + right) / 2) + const mid = dis[midindex] + const midclock = mid.clock + if (midclock <= clock) { + if (clock < midclock + mid.len) { + return midindex + } + left = midindex + 1 + } else { + right = midindex - 1 + } + } + return left < dis.length ? left : null +} + +/** + * @param {Array} idSets + * @return {IdSet} A fresh IdSet + */ +export const mergeIdSets = idSets => { + const merged = new IdSet() + for (let dssI = 0; dssI < idSets.length; dssI++) { + idSets[dssI].clients.forEach((rangesLeft, client) => { + if (!merged.clients.has(client)) { + // Write all missing keys from current ds and all following. + // If merged already contains `client` current ds has already been added. + const ids = rangesLeft.getIds().slice() + for (let i = dssI + 1; i < idSets.length; i++) { + const nextIds = idSets[i].clients.get(client) + if (nextIds) { + array.appendTo(ids, nextIds.getIds()) + } + } + merged.clients.set(client, new IdRanges(ids)) + } + }) + } + return merged +} + +/** + * @template {IdSet | IdMap} S + * @param {S} dest + * @param {S} src + */ +export const _insertIntoIdSet = (dest, src) => { + src.clients.forEach((srcRanges, client) => { + const targetRanges = dest.clients.get(client) + if (targetRanges) { + array.appendTo(targetRanges.getIds(), srcRanges.getIds()) + targetRanges.sorted = false + } else { + const res = srcRanges.copy() + res.sorted = true + dest.clients.set(client, /** @type {any} */ (res)) + } + }) +} + +/** + * @param {IdSet} dest + * @param {IdSet} src + */ +export const insertIntoIdSet = _insertIntoIdSet + +/** + * Remove all ranges from `exclude` from `ds`. The result is a fresh IdSet containing all ranges from `idSet` that are not + * in `exclude`. + * + * @template {IdSet | IdMap} Set + * @param {Set} set + * @param {IdSet | IdMap} exclude + * @return {Set} + */ +export const _diffSet = (set, exclude) => { + /** + * @type {Set} + */ + const res = /** @type {any } */ (set instanceof IdSet ? new IdSet() : new IdMap()) + const Ranges = set instanceof IdSet ? IdRanges : AttrRanges + set.clients.forEach((_setRanges, client) => { + /** + * @type {Array} + */ + let resRanges = [] + const _excludedRanges = exclude.clients.get(client) + const setRanges = _setRanges.getIds() + if (_excludedRanges == null) { + resRanges = setRanges.slice() + } else { + const excludedRanges = _excludedRanges.getIds() + let i = 0; let j = 0 + let currRange = setRanges[0] + while (i < setRanges.length && j < excludedRanges.length) { + const e = excludedRanges[j] + if (currRange.clock + currRange.len <= e.clock) { // no overlapping, use next range item + if (currRange.len > 0) resRanges.push(currRange) + currRange = setRanges[++i] + } else if (e.clock + e.len <= currRange.clock) { // no overlapping, use next excluded item + j++ + } else if (e.clock <= currRange.clock) { // exclude laps into range (we already know that the ranges somehow collide) + const newClock = e.clock + e.len + const newLen = currRange.clock + currRange.len - newClock + if (newLen > 0) { + currRange = currRange.copyWith(newClock, newLen) + j++ + } else { + // this item is completely overwritten. len=0. We can jump to the next range + currRange = setRanges[++i] + } + } else { // currRange.clock < e.clock -- range laps into exclude => adjust len + // beginning can't be empty, add it to the result + const nextLen = e.clock - currRange.clock + resRanges.push(currRange.copyWith(currRange.clock, nextLen)) + // retain the remaining length after exclude in currRange + currRange = currRange.copyWith(currRange.clock + e.len + nextLen, math.max(currRange.len - e.len - nextLen, 0)) + if (currRange.len === 0) currRange = setRanges[++i] + else j++ + } + } + if (currRange != null) { + resRanges.push(currRange) + } + i++ + while (i < setRanges.length) { + resRanges.push(setRanges[i++]) + } + } + // @ts-ignore + if (resRanges.length > 0) res.clients.set(client, /** @type {any} */ (new Ranges(resRanges))) + }) + return res +} + +/** + * Remove all ranges from `exclude` from `idSet`. The result is a fresh IdSet containing all ranges from `idSet` that are not + * in `exclude`. + * + * @type {(idSet: IdSet, exclude: IdSet|IdMap) => IdSet} + */ +export const diffIdSet = _diffSet + +/** + * @template {IdSet | IdMap} SetA + * @template {IdSet | IdMap} SetB + * @param {SetA} setA + * @param {SetB} setB + * @return {SetA extends IdMap ? (SetB extends IdMap ? IdMap : IdMap) : IdSet} + */ +export const _intersectSets = (setA, setB) => { + /** + * @type {IdMap | IdSet} + */ + const res = /** @type {any } */ (setA instanceof IdSet ? new IdSet() : new IdMap()) + const Ranges = setA instanceof IdSet ? IdRanges : AttrRanges + setA.clients.forEach((_aRanges, client) => { + /** + * @type {Array} + */ + const resRanges = [] + const _bRanges = setB.clients.get(client) + const aRanges = _aRanges.getIds() + if (_bRanges != null) { + const bRanges = _bRanges.getIds() + for (let a = 0, b = 0; a < aRanges.length && b < bRanges.length;) { + const aRange = aRanges[a] + const bRange = bRanges[b] + // construct overlap + const clock = math.max(aRange.clock, bRange.clock) + const len = math.min(aRange.len - (clock - aRange.clock), bRange.len - (clock - bRange.clock)) + if (len > 0) { + resRanges.push(aRange instanceof AttrRange + ? new AttrRange(clock, len, /** @type {Array>} */ (aRange.attrs).concat(bRange.attrs)) + : new IdRange(clock, len) + ) + } + if (aRange.clock + aRange.len < bRange.clock + bRange.len) { + a++ + } else { + b++ + } + } + } + // @ts-ignore + if (resRanges.length > 0) res.clients.set(client, /** @type {any} */ (new Ranges(resRanges))) + }) + return /** @type {any} */ (res) +} + +export const intersectSets = _intersectSets + +/** + * @param {IdSet} idSet + * @param {number} client + * @param {number} clock + * @param {number} length + * + * @private + * @function + */ +export const addToIdSet = (idSet, client, clock, length) => { + if (length === 0) return + const idRanges = idSet.clients.get(client) + if (idRanges) { + idRanges.add(clock, length) + } else { + idSet.clients.set(client, new IdRanges([new IdRange(clock, length)])) + } +} + +/** + * @param {IdSet} idSet + * @param {AbstractStruct} struct + * + * @private + * @function + */ +export const addStructToIdSet = (idSet, struct) => addToIdSet(idSet, struct.id.client, struct.id.clock, struct.length) + +export const createIdSet = () => new IdSet() + +/** + * @param {StructStore} ss + * @return {IdSet} + * + * @private + * @function + */ +export const createDeleteSetFromStructStore = ss => { + const ds = createIdSet() + ss.clients.forEach((structs, client) => { + /** + * @type {Array} + */ + const dsitems = [] + for (let i = 0; i < structs.length; i++) { + const struct = structs[i] + if (struct.deleted) { + const clock = struct.id.clock + let len = struct.length + if (i + 1 < structs.length) { + for (let next = structs[i + 1]; i + 1 < structs.length && next.deleted; next = structs[++i + 1]) { + len += next.length + } + } + dsitems.push(new IdRange(clock, len)) + } + } + if (dsitems.length > 0) { + ds.clients.set(client, new IdRanges(dsitems)) + } + }) + return ds +} + +/** + * @param {Array} structs + * @param {boolean} filterDeleted + * + */ +export const _createInsertSliceFromStructs = (structs, filterDeleted) => { + /** + * @type {Array} + */ + const iditems = [] + for (let i = 0; i < structs.length; i++) { + const struct = structs[i] + if (!(filterDeleted && struct.deleted)) { + const clock = struct.id.clock + let len = struct.length + if (i + 1 < structs.length) { + // eslint-disable-next-line + for (let next = structs[i + 1]; i + 1 < structs.length && !(filterDeleted && next.deleted); next = structs[++i + 1]) { + len += next.length + } + } + iditems.push(new IdRange(clock, len)) + } + } + return iditems +} + +/** + * @param {import('../internals.js').StructStore} ss + * @param {boolean} filterDeleted + */ +export const createInsertSetFromStructStore = (ss, filterDeleted) => { + const idset = createIdSet() + ss.clients.forEach((structs, client) => { + const iditems = _createInsertSliceFromStructs(structs, filterDeleted) + if (iditems.length !== 0) { + idset.clients.set(client, new IdRanges(iditems)) + } + }) + return idset +} + +/** + * @param {IdSetEncoderV1 | IdSetEncoderV2} encoder + * @param {IdSet} idSet + * + * @private + * @function + */ +export const writeIdSet = (encoder, idSet) => { + encoding.writeVarUint(encoder.restEncoder, idSet.clients.size) + // Ensure that the delete set is written in a deterministic order + array.from(idSet.clients.entries()) + .sort((a, b) => b[0] - a[0]) + .forEach(([client, _idRanges]) => { + const idRanges = _idRanges.getIds() + encoder.resetIdSetCurVal() + encoding.writeVarUint(encoder.restEncoder, client) + const len = idRanges.length + encoding.writeVarUint(encoder.restEncoder, len) + for (let i = 0; i < len; i++) { + const item = idRanges[i] + encoder.writeIdSetClock(item.clock) + encoder.writeIdSetLen(item.len) + } + }) +} + +/** + * @param {DSDecoderV1 | DSDecoderV2} decoder + * @return {IdSet} + * + * @private + * @function + */ +export const readIdSet = decoder => { + const ds = new IdSet() + const numClients = decoding.readVarUint(decoder.restDecoder) + for (let i = 0; i < numClients; i++) { + decoder.resetDsCurVal() + const client = decoding.readVarUint(decoder.restDecoder) + const numberOfDeletes = decoding.readVarUint(decoder.restDecoder) + if (numberOfDeletes > 0) { + /** + * @type {Array} + */ + const dsRanges = [] + for (let i = 0; i < numberOfDeletes; i++) { + dsRanges.push(new IdRange(decoder.readDsClock(), decoder.readDsLen())) + } + ds.clients.set(client, new IdRanges(dsRanges)) + } + } + return ds +} + +/** + * @todo YDecoder also contains references to String and other Decoders. Would make sense to exchange YDecoder.toUint8Array for YDecoder.DsToUint8Array().. + */ + +/** + * @param {DSDecoderV1 | DSDecoderV2} decoder + * @param {Transaction} transaction + * @param {StructStore} store + * @return {Uint8Array|null} Returns a v2 update containing all deletes that couldn't be applied yet; or null if all deletes were applied successfully. + * + * @private + * @function + */ +export const readAndApplyDeleteSet = (decoder, transaction, store) => { + const unappliedDS = new IdSet() + const numClients = decoding.readVarUint(decoder.restDecoder) + for (let i = 0; i < numClients; i++) { + decoder.resetDsCurVal() + const client = decoding.readVarUint(decoder.restDecoder) + const numberOfDeletes = decoding.readVarUint(decoder.restDecoder) + const structs = store.clients.get(client) || [] + const state = getState(store, client) + for (let i = 0; i < numberOfDeletes; i++) { + const clock = decoder.readDsClock() + const clockEnd = clock + decoder.readDsLen() + if (clock < state) { + if (state < clockEnd) { + addToIdSet(unappliedDS, client, state, clockEnd - state) + } + let index = findIndexSS(structs, clock) + /** + * We can ignore the case of GC and Delete structs, because we are going to skip them + * @type {Item | GC | Skip} + */ + let struct = structs[index] + // split the first item if necessary + if (!struct.deleted && struct.id.clock < clock && struct instanceof Item) { + // increment index, we now want to use the next struct + structs.splice(++index, 0, splitItem(transaction, struct, clock - struct.id.clock)) + } + while (index < structs.length) { + // @ts-ignore + struct = structs[index++] + if (struct.id.clock < clockEnd) { + if (!struct.deleted) { + if (struct instanceof Item) { + if (clockEnd < struct.id.clock + struct.length) { + structs.splice(index, 0, splitItem(transaction, struct, clockEnd - struct.id.clock)) + } + struct.delete(transaction) + } else { // is a Skip - add range to unappliedDS + const c = math.max(struct.id.clock, clock) + unappliedDS.add(client, c, math.min(struct.length, clockEnd - c)) + } + } + } else { + break + } + } + } else { + addToIdSet(unappliedDS, client, clock, clockEnd - clock) + } + } + } + if (unappliedDS.clients.size > 0) { + const ds = new UpdateEncoderV2() + encoding.writeVarUint(ds.restEncoder, 0) // encode 0 structs + writeIdSet(ds, unappliedDS) + return ds.toUint8Array() + } + return null +} + +/** + * @param {IdSet} ds1 + * @param {IdSet} ds2 + */ +export const equalIdSets = (ds1, ds2) => { + if (ds1.clients.size !== ds2.clients.size) return false + for (const [client, _deleteItems1] of ds1.clients.entries()) { + const deleteItems1 = _deleteItems1.getIds() + const deleteItems2 = ds2.clients.get(client)?.getIds() + if (deleteItems2 === undefined || deleteItems1.length !== deleteItems2.length) return false + for (let i = 0; i < deleteItems1.length; i++) { + const di1 = deleteItems1[i] + const di2 = deleteItems2[i] + if (di1.clock !== di2.clock || di1.len !== di2.len) { + return false + } + } + } + return true +} diff --git a/src/utils/PermanentUserData.js b/src/utils/PermanentUserData.js deleted file mode 100644 index 6ebd33b18..000000000 --- a/src/utils/PermanentUserData.js +++ /dev/null @@ -1,142 +0,0 @@ - -import { - YArray, - YMap, - readDeleteSet, - writeDeleteSet, - createDeleteSet, - DSEncoderV1, DSDecoderV1, ID, DeleteSet, YArrayEvent, Transaction, Doc // eslint-disable-line -} from '../internals.js' - -import * as decoding from 'lib0/decoding' - -import { mergeDeleteSets, isDeleted } from './DeleteSet.js' - -export class PermanentUserData { - /** - * @param {Doc} doc - * @param {YMap} [storeType] - */ - constructor (doc, storeType = doc.getMap('users')) { - /** - * @type {Map} - */ - const dss = new Map() - this.yusers = storeType - this.doc = doc - /** - * Maps from clientid to userDescription - * - * @type {Map} - */ - this.clients = new Map() - this.dss = dss - /** - * @param {YMap} user - * @param {string} userDescription - */ - const initUser = (user, userDescription) => { - /** - * @type {YArray} - */ - const ds = user.get('ds') - const ids = user.get('ids') - const addClientId = /** @param {number} clientid */ clientid => this.clients.set(clientid, userDescription) - ds.observe(/** @param {YArrayEvent} event */ event => { - event.changes.added.forEach(item => { - item.content.getContent().forEach(encodedDs => { - if (encodedDs instanceof Uint8Array) { - this.dss.set(userDescription, mergeDeleteSets([this.dss.get(userDescription) || createDeleteSet(), readDeleteSet(new DSDecoderV1(decoding.createDecoder(encodedDs)))])) - } - }) - }) - }) - this.dss.set(userDescription, mergeDeleteSets(ds.map(encodedDs => readDeleteSet(new DSDecoderV1(decoding.createDecoder(encodedDs)))))) - ids.observe(/** @param {YArrayEvent} event */ event => - event.changes.added.forEach(item => item.content.getContent().forEach(addClientId)) - ) - ids.forEach(addClientId) - } - // observe users - storeType.observe(event => { - event.keysChanged.forEach(userDescription => - initUser(storeType.get(userDescription), userDescription) - ) - }) - // add intial data - storeType.forEach(initUser) - } - - /** - * @param {Doc} doc - * @param {number} clientid - * @param {string} userDescription - * @param {Object} [conf] - * @param {function(Transaction, DeleteSet):boolean} [conf.filter] - */ - setUserMapping (doc, clientid, userDescription, { filter = () => true } = {}) { - const users = this.yusers - let user = users.get(userDescription) - if (!user) { - user = new YMap() - user.set('ids', new YArray()) - user.set('ds', new YArray()) - users.set(userDescription, user) - } - user.get('ids').push([clientid]) - users.observe(event => { - setTimeout(() => { - const userOverwrite = users.get(userDescription) - if (userOverwrite !== user) { - // user was overwritten, port all data over to the next user object - // @todo Experiment with Y.Sets here - user = userOverwrite - // @todo iterate over old type - this.clients.forEach((_userDescription, clientid) => { - if (userDescription === _userDescription) { - user.get('ids').push([clientid]) - } - }) - const encoder = new DSEncoderV1() - const ds = this.dss.get(userDescription) - if (ds) { - writeDeleteSet(encoder, ds) - user.get('ds').push([encoder.toUint8Array()]) - } - } - }, 0) - }) - doc.on('afterTransaction', /** @param {Transaction} transaction */ transaction => { - setTimeout(() => { - const yds = user.get('ds') - const ds = transaction.deleteSet - if (transaction.local && ds.clients.size > 0 && filter(transaction, ds)) { - const encoder = new DSEncoderV1() - writeDeleteSet(encoder, ds) - yds.push([encoder.toUint8Array()]) - } - }) - }) - } - - /** - * @param {number} clientid - * @return {any} - */ - getUserByClientId (clientid) { - return this.clients.get(clientid) || null - } - - /** - * @param {ID} id - * @return {string | null} - */ - getUserByDeletedId (id) { - for (const [userDescription, ds] of this.dss.entries()) { - if (isDeleted(ds, id)) { - return userDescription - } - } - return null - } -} diff --git a/src/utils/RelativePosition.js b/src/utils/RelativePosition.js index 614c0bc54..c89aa2660 100644 --- a/src/utils/RelativePosition.js +++ b/src/utils/RelativePosition.js @@ -1,4 +1,3 @@ - import { writeID, readID, @@ -9,7 +8,8 @@ import { createID, ContentType, followRedone, - ID, Doc, AbstractType // eslint-disable-line + getItem, + StructStore, ID, Doc, AbstractType, noAttributionsManager, // eslint-disable-line } from '../internals.js' import * as encoding from 'lib0/encoding' @@ -66,7 +66,7 @@ export class RelativePosition { * after the meant position. * I.e. position 1 in 'ab' is associated to character 'b'. * - * If assoc < 0, then the relative position is associated to the caharacter + * If assoc < 0, then the relative position is associated to the character * before the meant position. * * @type {number} @@ -102,7 +102,7 @@ export const relativePositionToJSON = rpos => { * * @function */ -export const createRelativePositionFromJSON = json => new RelativePosition(json.type == null ? null : createID(json.type.client, json.type.clock), json.tname || null, json.item == null ? null : createID(json.item.client, json.item.clock), json.assoc == null ? 0 : json.assoc) +export const createRelativePositionFromJSON = json => new RelativePosition(json.type == null ? null : createID(json.type.client, json.type.clock), json.tname ?? null, json.item == null ? null : createID(json.item.client, json.item.clock), json.assoc == null ? 0 : json.assoc) export class AbsolutePosition { /** @@ -153,14 +153,15 @@ export const createRelativePosition = (type, item, assoc) => { /** * Create a relativePosition based on a absolute position. * - * @param {AbstractType} type The base type (e.g. YText or YArray). + * @param {AbstractType} type The base type (e.g. YText or YArray). * @param {number} index The absolute position. * @param {number} [assoc] + * @param {import('../utils/AttributionManager.js').AbstractAttributionManager} attributionManager * @return {RelativePosition} * * @function */ -export const createRelativePositionFromTypeIndex = (type, index, assoc = 0) => { +export const createRelativePositionFromTypeIndex = (type, index, assoc = 0, attributionManager = noAttributionsManager) => { let t = type._start if (assoc < 0) { // associated to the left character or the beginning of a type, increment index if possible. @@ -170,13 +171,12 @@ export const createRelativePositionFromTypeIndex = (type, index, assoc = 0) => { index-- } while (t !== null) { - if (!t.deleted && t.countable) { - if (t.length > index) { - // case 1: found position somewhere in the linked list - return createRelativePosition(type, createID(t.id.client, t.id.clock + index), assoc) - } - index -= t.length + const len = attributionManager.contentLength(t) + if (len > index) { + // case 1: found position somewhere in the linked list + return createRelativePosition(type, createID(t.id.client, t.id.clock + index), assoc) } + index -= len if (t.right === null && assoc < 0) { // left-associated position, return last available id return createRelativePosition(type, t.lastId, assoc) @@ -257,13 +257,37 @@ export const readRelativePosition = decoder => { export const decodeRelativePosition = uint8Array => readRelativePosition(decoding.createDecoder(uint8Array)) /** + * @param {StructStore} store + * @param {ID} id + */ +const getItemWithOffset = (store, id) => { + const item = getItem(store, id) + const diff = id.clock - item.id.clock + return { + item, diff + } +} + +/** + * Transform a relative position to an absolute position. + * + * If you want to share the relative position with other users, you should set + * `followUndoneDeletions` to false to get consistent results across all clients. + * + * When calculating the absolute position, we try to follow the "undone deletions". This yields + * better results for the user who performed undo. However, only the user who performed the undo + * will get the better results, the other users don't know which operations recreated a deleted + * range of content. There is more information in this ticket: https://github.com/yjs/yjs/issues/638 + * * @param {RelativePosition} rpos * @param {Doc} doc + * @param {boolean} followUndoneDeletions - whether to follow undone deletions - see https://github.com/yjs/yjs/issues/638 + * @param {import('../utils/AttributionManager.js').AbstractAttributionManager} attributionManager * @return {AbsolutePosition|null} * * @function */ -export const createAbsolutePositionFromRelativePosition = (rpos, doc) => { +export const createAbsolutePositionFromRelativePosition = (rpos, doc, followUndoneDeletions = true, attributionManager = noAttributionsManager) => { const store = doc.store const rightID = rpos.item const typeID = rpos.type @@ -275,19 +299,17 @@ export const createAbsolutePositionFromRelativePosition = (rpos, doc) => { if (getState(store, rightID.client) <= rightID.clock) { return null } - const res = followRedone(store, rightID) + const res = followUndoneDeletions ? followRedone(store, rightID) : getItemWithOffset(store, rightID) const right = res.item if (!(right instanceof Item)) { return null } type = /** @type {AbstractType} */ (right.parent) if (type._item === null || !type._item.deleted) { - index = (right.deleted || !right.countable) ? 0 : (res.diff + (assoc >= 0 ? 0 : 1)) // adjust position based on left association if necessary + index = attributionManager.contentLength(right) === 0 ? 0 : (res.diff + (assoc >= 0 ? 0 : 1)) // adjust position based on left association if necessary let n = right.left while (n !== null) { - if (!n.deleted && n.countable) { - index += n.length - } + index += attributionManager.contentLength(n) n = n.left } } @@ -299,7 +321,7 @@ export const createAbsolutePositionFromRelativePosition = (rpos, doc) => { // type does not exist yet return null } - const { item } = followRedone(store, typeID) + const { item } = followUndoneDeletions ? followRedone(store, typeID) : { item: getItem(store, typeID) } if (item instanceof Item && item.content instanceof ContentType) { type = item.content.type } else { diff --git a/src/utils/Snapshot.js b/src/utils/Snapshot.js index c9aa19e9b..c585a9a24 100644 --- a/src/utils/Snapshot.js +++ b/src/utils/Snapshot.js @@ -1,21 +1,22 @@ - import { - isDeleted, createDeleteSetFromStructStore, getStateVector, getItemCleanStart, - iterateDeletedStructs, - writeDeleteSet, + iterateStructsByIdSet, + writeIdSet, writeStateVector, - readDeleteSet, + readIdSet, readStateVector, - createDeleteSet, + createIdSet, createID, getState, findIndexSS, UpdateEncoderV2, applyUpdateV2, - DSEncoderV1, DSEncoderV2, DSDecoderV1, DSDecoderV2, Transaction, Doc, DeleteSet, Item // eslint-disable-line + LazyStructReader, + equalIdSets, + UpdateDecoderV1, UpdateDecoderV2, IdSetEncoderV1, IdSetEncoderV2, DSDecoderV1, DSDecoderV2, Transaction, Doc, IdSet, Item, // eslint-disable-line + mergeIdSets } from '../internals.js' import * as map from 'lib0/map' @@ -25,12 +26,12 @@ import * as encoding from 'lib0/encoding' export class Snapshot { /** - * @param {DeleteSet} ds + * @param {IdSet} ds * @param {Map} sv state map */ constructor (ds, sv) { /** - * @type {DeleteSet} + * @type {IdSet} */ this.ds = ds /** @@ -47,11 +48,9 @@ export class Snapshot { * @return {boolean} */ export const equalSnapshots = (snap1, snap2) => { - const ds1 = snap1.ds.clients - const ds2 = snap2.ds.clients const sv1 = snap1.sv const sv2 = snap2.sv - if (sv1.size !== sv2.size || ds1.size !== ds2.size) { + if (sv1.size !== sv2.size) { return false } for (const [key, value] of sv1.entries()) { @@ -59,29 +58,16 @@ export const equalSnapshots = (snap1, snap2) => { return false } } - for (const [client, dsitems1] of ds1.entries()) { - const dsitems2 = ds2.get(client) || [] - if (dsitems1.length !== dsitems2.length) { - return false - } - for (let i = 0; i < dsitems1.length; i++) { - const dsitem1 = dsitems1[i] - const dsitem2 = dsitems2[i] - if (dsitem1.clock !== dsitem2.clock || dsitem1.len !== dsitem2.len) { - return false - } - } - } - return true + return equalIdSets(snap1.ds, snap2.ds) } /** * @param {Snapshot} snapshot - * @param {DSEncoderV1 | DSEncoderV2} [encoder] + * @param {IdSetEncoderV1 | IdSetEncoderV2} [encoder] * @return {Uint8Array} */ -export const encodeSnapshotV2 = (snapshot, encoder = new DSEncoderV2()) => { - writeDeleteSet(encoder, snapshot.ds) +export const encodeSnapshotV2 = (snapshot, encoder = new IdSetEncoderV2()) => { + writeIdSet(encoder, snapshot.ds) writeStateVector(encoder, snapshot.sv) return encoder.toUint8Array() } @@ -90,7 +76,7 @@ export const encodeSnapshotV2 = (snapshot, encoder = new DSEncoderV2()) => { * @param {Snapshot} snapshot * @return {Uint8Array} */ -export const encodeSnapshot = snapshot => encodeSnapshotV2(snapshot, new DSEncoderV1()) +export const encodeSnapshot = snapshot => encodeSnapshotV2(snapshot, new IdSetEncoderV1()) /** * @param {Uint8Array} buf @@ -98,7 +84,7 @@ export const encodeSnapshot = snapshot => encodeSnapshotV2(snapshot, new DSEncod * @return {Snapshot} */ export const decodeSnapshotV2 = (buf, decoder = new DSDecoderV2(decoding.createDecoder(buf))) => { - return new Snapshot(readDeleteSet(decoder), readStateVector(decoder)) + return new Snapshot(readIdSet(decoder), readStateVector(decoder)) } /** @@ -108,13 +94,13 @@ export const decodeSnapshotV2 = (buf, decoder = new DSDecoderV2(decoding.createD export const decodeSnapshot = buf => decodeSnapshotV2(buf, new DSDecoderV1(decoding.createDecoder(buf))) /** - * @param {DeleteSet} ds + * @param {IdSet} ds * @param {Map} sm * @return {Snapshot} */ export const createSnapshot = (ds, sm) => new Snapshot(ds, sm) -export const emptySnapshot = createSnapshot(createDeleteSet(), new Map()) +export const emptySnapshot = createSnapshot(createIdSet(), new Map()) /** * @param {Doc} doc @@ -131,7 +117,7 @@ export const snapshot = doc => createSnapshot(createDeleteSetFromStructStore(doc */ export const isVisible = (item, snapshot) => snapshot === undefined ? !item.deleted - : snapshot.sv.has(item.id.client) && (snapshot.sv.get(item.id.client) || 0) > item.id.clock && !isDeleted(snapshot.ds, item.id) + : snapshot.sv.has(item.id.client) && (snapshot.sv.get(item.id.client) || 0) > item.id.clock && !snapshot.ds.hasId(item.id) /** * @param {Transaction} transaction @@ -147,12 +133,20 @@ export const splitSnapshotAffectedStructs = (transaction, snapshot) => { getItemCleanStart(transaction, createID(client, clock)) } }) - iterateDeletedStructs(transaction, snapshot.ds, item => {}) + iterateStructsByIdSet(transaction, snapshot.ds, _item => {}) meta.add(snapshot) } } /** + * @example + * const ydoc = new Y.Doc({ gc: false }) + * ydoc.getText().insert(0, 'world!') + * const snapshot = Y.snapshot(ydoc) + * ydoc.getText().insert(0, 'hello ') + * const restored = Y.createDocFromSnapshot(ydoc, snapshot) + * assert(restored.getText().toString() === 'world!') + * * @param {Doc} originDoc * @param {Snapshot} snapshot * @param {Doc} [newDoc] Optionally, you may define the Yjs document that receives the data from originDoc @@ -161,7 +155,7 @@ export const splitSnapshotAffectedStructs = (transaction, snapshot) => { export const createDocFromSnapshot = (originDoc, snapshot, newDoc = new Doc()) => { if (originDoc.gc) { // we should not try to restore a GC-ed document, because some of the restored items might have their content deleted - throw new Error('originDoc must not be garbage collected') + throw new Error('Garbage-collection must be disabled in `originDoc`!') } const { sv, ds } = snapshot @@ -190,12 +184,37 @@ export const createDocFromSnapshot = (originDoc, snapshot, newDoc = new Doc()) = // first clock written is 0 encoding.writeVarUint(encoder.restEncoder, 0) for (let i = 0; i <= lastStructIndex; i++) { - structs[i].write(encoder, 0) + structs[i].write(encoder, 0, 0) } } - writeDeleteSet(encoder, ds) + writeIdSet(encoder, ds) }) applyUpdateV2(newDoc, encoder.toUint8Array(), 'snapshot') return newDoc } + +/** + * @param {Snapshot} snapshot + * @param {Uint8Array} update + * @param {typeof UpdateDecoderV2 | typeof UpdateDecoderV1} [YDecoder] + */ +export const snapshotContainsUpdateV2 = (snapshot, update, YDecoder = UpdateDecoderV2) => { + const structs = [] + const updateDecoder = new YDecoder(decoding.createDecoder(update)) + const lazyDecoder = new LazyStructReader(updateDecoder, false) + for (let curr = lazyDecoder.curr; curr !== null; curr = lazyDecoder.next()) { + structs.push(curr) + if ((snapshot.sv.get(curr.id.client) || 0) < curr.id.clock + curr.length) { + return false + } + } + const mergedDS = mergeIdSets([snapshot.ds, readIdSet(updateDecoder)]) + return equalIdSets(snapshot.ds, mergedDS) +} + +/** + * @param {Snapshot} snapshot + * @param {Uint8Array} update + */ +export const snapshotContainsUpdate = (snapshot, update) => snapshotContainsUpdateV2(snapshot, update, UpdateDecoderV1) diff --git a/src/utils/StructSet.js b/src/utils/StructSet.js new file mode 100644 index 000000000..4af317481 --- /dev/null +++ b/src/utils/StructSet.js @@ -0,0 +1,137 @@ +import { + createID, + readItemContent, + findIndexCleanStart, + Skip, + UpdateDecoderV1, UpdateDecoderV2, IdSet, Doc, GC, Item, ID, // eslint-disable-line +} from '../internals.js' + +import * as decoding from 'lib0/decoding' +import * as binary from 'lib0/binary' +import * as map from 'lib0/map' + +/** + * @param {UpdateDecoderV1 | UpdateDecoderV2} decoder The decoder object to read data from. + * @param {Doc} doc + * @return {StructSet} + * + * @private + * @function + */ +export const readStructSet = (decoder, doc) => { + const clientRefs = new StructSet() + const numOfStateUpdates = decoding.readVarUint(decoder.restDecoder) + for (let i = 0; i < numOfStateUpdates; i++) { + const numberOfStructs = decoding.readVarUint(decoder.restDecoder) + /** + * @type {Array} + */ + const refs = new Array(numberOfStructs) + const client = decoder.readClient() + let clock = decoding.readVarUint(decoder.restDecoder) + clientRefs.clients.set(client, new StructRange(refs)) + for (let i = 0; i < numberOfStructs; i++) { + const info = decoder.readInfo() + switch (binary.BITS5 & info) { + case 0: { // GC + const len = decoder.readLen() + refs[i] = new GC(createID(client, clock), len) + clock += len + break + } + case 10: { // Skip Struct (nothing to apply) + // @todo we could reduce the amount of checks by adding Skip struct to clientRefs so we know that something is missing. + const len = decoding.readVarUint(decoder.restDecoder) + refs[i] = new Skip(createID(client, clock), len) + clock += len + break + } + default: { // Item with content + /** + * The optimized implementation doesn't use any variables because inlining variables is faster. + * Below a non-optimized version is shown that implements the basic algorithm with + * a few comments + */ + const cantCopyParentInfo = (info & (binary.BIT7 | binary.BIT8)) === 0 + // If parent = null and neither left nor right are defined, then we know that `parent` is child of `y` + // and we read the next string as parentYKey. + // It indicates how we store/retrieve parent from `y.share` + // @type {string|null} + const struct = new Item( + createID(client, clock), + null, // left + (info & binary.BIT8) === binary.BIT8 ? decoder.readLeftID() : null, // origin + null, // right + (info & binary.BIT7) === binary.BIT7 ? decoder.readRightID() : null, // right origin + cantCopyParentInfo ? (decoder.readParentInfo() ? doc.get(decoder.readString()) : decoder.readLeftID()) : null, // parent + cantCopyParentInfo && (info & binary.BIT6) === binary.BIT6 ? decoder.readString() : null, // parentSub + readItemContent(decoder, info) // item content + ) + refs[i] = struct + clock += struct.length + } + } + } + } + return clientRefs +} + +/** + * Remove item-ranges from the StructSet. + * + * @param {StructSet} ss + * @param {IdSet} exclude + */ +export const removeRangesFromStructSet = (ss, exclude) => { + // @todo walk through ss instead to reduce iterations + exclude.clients.forEach((range, client) => { + const structs = /** @type {StructRange} */ (ss.clients.get(client))?.refs + if (structs != null) { + const firstStruct = structs[0] + const lastStruct = structs[structs.length - 1] + const idranges = range.getIds() + for (let i = 0; i < idranges.length; i++) { + const range = idranges[i] + let startIndex = 0 + if (range.clock >= lastStruct.id.clock + lastStruct.length) continue + if (range.clock > firstStruct.id.clock) { + startIndex = findIndexCleanStart(null, structs, range.clock) + } + let endIndex = structs.length // must be set here, after structs is modified + if (range.clock + range.len <= firstStruct.id.clock) continue + if (range.clock + range.len < lastStruct.id.clock + lastStruct.length) { + endIndex = findIndexCleanStart(null, structs, range.clock + range.len) + } + if (startIndex < endIndex) { + structs[startIndex] = new Skip(new ID(client, range.clock), range.len) + const d = endIndex - startIndex + if (d > 1) { + structs.splice(startIndex + 1, d - 1) + } + } + } + } + }) +} + +class StructRange { + /** + * @param {Array} refs + */ + constructor (refs) { + this.i = 0 + /** + * @type {Array} + */ + this.refs = refs + } +} + +export class StructSet { + constructor () { + /** + * @type {Map} + */ + this.clients = map.create() + } +} diff --git a/src/utils/StructStore.js b/src/utils/StructStore.js index 7a2e256c7..78b734a75 100644 --- a/src/utils/StructStore.js +++ b/src/utils/StructStore.js @@ -1,8 +1,12 @@ - import { GC, splitItem, - Transaction, ID, Item, DSDecoderV2 // eslint-disable-line + createDeleteSetFromStructStore, + createIdSet, + Transaction, ID, Item, // eslint-disable-line + Skip, + createID, + splitStruct } from '../internals.js' import * as math from 'lib0/math' @@ -14,6 +18,7 @@ export class StructStore { * @type {Map>} */ this.clients = new Map() + // this.ds = new IdSet() /** * @type {null | { missing: Map, update: Uint8Array }} */ @@ -22,6 +27,11 @@ export class StructStore { * @type {null | Uint8Array} */ this.pendingDs = null + this.skips = createIdSet() + } + + get ds () { + return createDeleteSetFromStructStore(this) } } @@ -41,6 +51,9 @@ export const getStateVector = store => { const struct = structs[structs.length - 1] sm.set(client, struct.id.clock + struct.length) }) + store.skips.clients.forEach((range, client) => { + sm.set(client, range.getIds()[0].clock) + }) return sm } @@ -67,13 +80,13 @@ export const getState = (store, client) => { * @private * @function */ -export const integretyCheck = store => { +export const integrityCheck = store => { store.clients.forEach(structs => { for (let i = 1; i < structs.length; i++) { const l = structs[i - 1] const r = structs[i] if (l.id.clock + l.length !== r.id.clock) { - throw new Error('StructStore failed integrety check') + throw new Error('StructStore failed integrity check') } } }) @@ -94,7 +107,20 @@ export const addStruct = (store, struct) => { } else { const lastStruct = structs[structs.length - 1] if (lastStruct.id.clock + lastStruct.length !== struct.id.clock) { - throw error.unexpectedCase() + // this replaces an integrated skip + let index = findIndexSS(structs, struct.id.clock) + const skip = structs[index] + const diffStart = struct.id.clock - skip.id.clock + const diffEnd = skip.id.clock + skip.length - struct.id.clock - struct.length + if (diffStart > 0) { + structs.splice(index++, 0, new Skip(createID(struct.id.client, skip.id.clock), diffStart)) + } + if (diffEnd > 0) { + structs.splice(index + 1, 0, new Skip(createID(struct.id.client, struct.id.clock + struct.length), diffEnd)) + } + structs[index] = struct + store.skips.delete(struct.id.client, struct.id.clock, struct.length) + return } } structs.push(struct) @@ -166,15 +192,15 @@ export const find = (store, id) => { export const getItem = /** @type {function(StructStore,ID):Item} */ (find) /** - * @param {Transaction} transaction + * @param {Transaction?} transaction * @param {Array} structs * @param {number} clock */ export const findIndexCleanStart = (transaction, structs, clock) => { const index = findIndexSS(structs, clock) const struct = structs[index] - if (struct.id.clock < clock && struct instanceof Item) { - structs.splice(index + 1, 0, splitItem(transaction, struct, clock - struct.id.clock)) + if (struct.id.clock < clock) { + structs.splice(index + 1, 0, splitStruct(transaction, struct, clock - struct.id.clock)) return index + 1 } return index @@ -222,16 +248,17 @@ export const getItemCleanEnd = (transaction, store, id) => { /** * Replace `item` with `newitem` in store - * @param {StructStore} store + * @param {Transaction} tr * @param {GC|Item} struct * @param {GC|Item} newStruct * * @private * @function */ -export const replaceStruct = (store, struct, newStruct) => { - const structs = /** @type {Array} */ (store.clients.get(struct.id.client)) +export const replaceStruct = (tr, struct, newStruct) => { + const structs = /** @type {Array} */ (tr.doc.store.clients.get(struct.id.client)) structs[findIndexSS(structs, struct.id.clock)] = newStruct + tr._mergeStructs.push(newStruct) } /** diff --git a/src/utils/Transaction.js b/src/utils/Transaction.js index a933056c9..ea34e5719 100644 --- a/src/utils/Transaction.js +++ b/src/utils/Transaction.js @@ -1,19 +1,19 @@ - import { getState, writeStructsFromTransaction, - writeDeleteSet, - DeleteSet, - sortAndMergeDeleteSet, + writeIdSet, getStateVector, findIndexSS, callEventHandlerListeners, + createIdSet, Item, generateNewClientId, createID, - UpdateEncoderV1, UpdateEncoderV2, GC, StructStore, AbstractType, AbstractStruct, YEvent, Doc // eslint-disable-line + cleanupYTextAfterTransaction, + IdSet, UpdateEncoderV1, UpdateEncoderV2, GC, StructStore, AbstractType, AbstractStruct, YEvent, Doc // eslint-disable-line } from '../internals.js' +import * as error from 'lib0/error' import * as map from 'lib0/map' import * as math from 'lib0/math' import * as set from 'lib0/set' @@ -28,7 +28,8 @@ import { callAll } from 'lib0/function' * possible. Here is an example to illustrate the advantages of bundling: * * @example - * const map = y.define('map', YMap) + * const ydoc = new Y.Doc() + * const map = ydoc.getMap('map') * // Log content when change is triggered * map.observe(() => { * console.log('change triggered') @@ -37,7 +38,7 @@ import { callAll } from 'lib0/function' * map.set('a', 0) // => "change triggered" * map.set('b', 0) // => "change triggered" * // When put in a transaction, it will trigger the log after the transaction: - * y.transact(() => { + * ydoc.transact(() => { * map.set('a', 1) * map.set('b', 1) * }) // => "change triggered" @@ -58,30 +59,38 @@ export class Transaction { this.doc = doc /** * Describes the set of deleted items by ids - * @type {DeleteSet} */ - this.deleteSet = new DeleteSet() + this.deleteSet = createIdSet() + /** + * Describes the set of items that are cleaned up / deleted by ids. It is a subset of + * this.deleteSet + */ + this.cleanUps = createIdSet() + /** + * Describes the set of inserted items by ids + */ + this.insertSet = createIdSet() /** * Holds the state before the transaction started. - * @type {Map} + * @type {Map?} */ - this.beforeState = getStateVector(doc.store) + this._beforeState = null /** * Holds the state after the transaction. - * @type {Map} + * @type {Map?} */ - this.afterState = new Map() + this._afterState = null /** * All types that were directly modified (property added or child * inserted/deleted). New types are not included in this Set. * Maps from type to parentSubs (`item.parentSub = null` for YArray) - * @type {Map>,Set>} + * @type {Map>} */ this.changed = new Map() /** * Stores the events for the types that observe also child elements. * It is mainly used by `observeDeep`. - * @type {Map>,Array>>} + * @type {Map>>} */ this.changedParentTypes = new Map() /** @@ -114,6 +123,48 @@ export class Transaction { * @type {Set} */ this.subdocsLoaded = new Set() + /** + * @type {boolean} + */ + this._needFormattingCleanup = false + this._done = false + } + + /** + * Holds the state before the transaction started. + * + * @deprecated + * @type {Map} + */ + get beforeState () { + if (this._beforeState == null) { + const sv = getStateVector(this.doc.store) + this.insertSet.clients.forEach((ranges, client) => { + sv.set(client, ranges.getIds()[0].clock) + }) + this._beforeState = sv + } + return this._beforeState + } + + /** + * Holds the state after the transaction. + * + * @deprecated + * @type {Map} + */ + get afterState () { + if (!this._done) error.unexpectedCase() + if (this._afterState == null) { + const sv = getStateVector(this.doc.store) + this.insertSet.clients.forEach((_ranges, client) => { + const ranges = _ranges.getIds() + const d = ranges[ranges.length - 1] + sv.set(client, d.clock + d.len) + }) + this._afterState = sv + } + return this._afterState } } @@ -123,12 +174,11 @@ export class Transaction { * @return {boolean} Whether data was written. */ export const writeUpdateMessageFromTransaction = (encoder, transaction) => { - if (transaction.deleteSet.clients.size === 0 && !map.any(transaction.afterState, (clock, client) => transaction.beforeState.get(client) !== clock)) { + if (transaction.deleteSet.clients.size === 0 && transaction.insertSet.clients.size === 0) { return false } - sortAndMergeDeleteSet(transaction.deleteSet) writeStructsFromTransaction(encoder, transaction) - writeDeleteSet(encoder, transaction.deleteSet) + writeIdSet(encoder, transaction.deleteSet) return true } @@ -148,12 +198,12 @@ export const nextID = transaction => { * did not change, it was just added and we should not fire events for `type`. * * @param {Transaction} transaction - * @param {AbstractType>} type + * @param {import('../utils/types.js').YType} type * @param {string|null} parentSub */ export const addChangedTypeToTransaction = (transaction, type, parentSub) => { const item = type._item - if (item === null || (item.id.clock < (transaction.beforeState.get(item.id.client) || 0) && !item.deleted)) { + if (item === null || (!item.deleted && !transaction.insertSet.hasId(item.id))) { map.setIfUndefined(transaction.changed, type, set.create).add(parentSub) } } @@ -161,28 +211,40 @@ export const addChangedTypeToTransaction = (transaction, type, parentSub) => { /** * @param {Array} structs * @param {number} pos + * @return {number} # of merged structs */ -const tryToMergeWithLeft = (structs, pos) => { - const left = structs[pos - 1] - const right = structs[pos] - if (left.deleted === right.deleted && left.constructor === right.constructor) { - if (left.mergeWith(right)) { - structs.splice(pos, 1) - if (right instanceof Item && right.parentSub !== null && /** @type {AbstractType} */ (right.parent)._map.get(right.parentSub) === right) { - /** @type {AbstractType} */ (right.parent)._map.set(right.parentSub, /** @type {Item} */ (left)) +const tryToMergeWithLefts = (structs, pos) => { + let right = structs[pos] + let left = structs[pos - 1] + let i = pos + for (; i > 0; right = left, left = structs[--i - 1]) { + if (left.deleted === right.deleted && left.constructor === right.constructor) { + if (left.mergeWith(right)) { + if (right instanceof Item && right.parentSub !== null && /** @type {AbstractType} */ (right.parent)._map.get(right.parentSub) === right) { + /** @type {AbstractType} */ (right.parent)._map.set(right.parentSub, /** @type {Item} */ (left)) + } + continue } } + break } + const merged = pos - i + if (merged) { + // remove all merged structs from the array + structs.splice(pos + 1 - merged, merged) + } + return merged } /** - * @param {DeleteSet} ds - * @param {StructStore} store + * @param {Transaction} tr + * @param {IdSet} ds * @param {function(Item):boolean} gcFilter */ -const tryGcDeleteSet = (ds, store, gcFilter) => { - for (const [client, deleteItems] of ds.clients.entries()) { - const structs = /** @type {Array} */ (store.clients.get(client)) +const tryGcDeleteSet = (tr, ds, gcFilter) => { + for (const [client, _deleteItems] of ds.clients.entries()) { + const deleteItems = _deleteItems.getIds() + const structs = /** @type {Array} */ (tr.doc.store.clients.get(client)) for (let di = deleteItems.length - 1; di >= 0; di--) { const deleteItem = deleteItems[di] const endDeleteItemClock = deleteItem.clock + deleteItem.len @@ -196,7 +258,7 @@ const tryGcDeleteSet = (ds, store, gcFilter) => { break } if (struct instanceof Item && struct.deleted && !struct.keep && gcFilter(struct)) { - struct.gc(store, false) + struct.gc(tr, false) } } } @@ -204,13 +266,14 @@ const tryGcDeleteSet = (ds, store, gcFilter) => { } /** - * @param {DeleteSet} ds + * @param {IdSet} ds * @param {StructStore} store */ -const tryMergeDeleteSet = (ds, store) => { +const tryMerge = (ds, store) => { // try to merge deleted / gc'd items - // merge from right to left for better efficiecy and so we don't miss any merge targets - ds.clients.forEach((deleteItems, client) => { + // merge from right to left for better efficiency and so we don't miss any merge targets + ds.clients.forEach((_deleteItems, client) => { + const deleteItems = _deleteItems.getIds() const structs = /** @type {Array} */ (store.clients.get(client)) for (let di = deleteItems.length - 1; di >= 0; di--) { const deleteItem = deleteItems[di] @@ -219,22 +282,22 @@ const tryMergeDeleteSet = (ds, store) => { for ( let si = mostRightIndexToCheck, struct = structs[si]; si > 0 && struct.id.clock >= deleteItem.clock; - struct = structs[--si] + struct = structs[si] ) { - tryToMergeWithLeft(structs, si) + si -= 1 + tryToMergeWithLefts(structs, si) } } }) } /** - * @param {DeleteSet} ds - * @param {StructStore} store + * @param {Transaction} tr + * @param {IdSet} idset * @param {function(Item):boolean} gcFilter */ -export const tryGc = (ds, store, gcFilter) => { - tryGcDeleteSet(ds, store, gcFilter) - tryMergeDeleteSet(ds, store) +export const tryGc = (tr, idset, gcFilter) => { + tryGcDeleteSet(tr, idset, gcFilter) + tryMerge(idset, tr.doc.store) } /** @@ -244,14 +307,13 @@ export const tryGc = (ds, store, gcFilter) => { const cleanupTransactions = (transactionCleanups, i) => { if (i < transactionCleanups.length) { const transaction = transactionCleanups[i] + transaction._done = true const doc = transaction.doc const store = doc.store const ds = transaction.deleteSet const mergeStructs = transaction._mergeStructs + // insertIntoIdSet(store.ds, ds) try { - sortAndMergeDeleteSet(ds) - transaction.afterState = getStateVector(transaction.doc.store) - doc._transaction = null doc.emit('beforeObserverCalls', [transaction, doc]) /** * An array of event callbacks. @@ -271,66 +333,69 @@ const cleanupTransactions = (transactionCleanups, i) => { ) fs.push(() => { // deep observe events - transaction.changedParentTypes.forEach((events, type) => - fs.push(() => { - // We need to think about the possibility that the user transforms the - // Y.Doc in the event. - if (type._item === null || !type._item.deleted) { - events = events - .filter(event => - event.target._item === null || !event.target._item.deleted - ) - events - .forEach(event => { - event.currentTarget = type - }) - // sort events by path length so that top-level events are fired first. - events - .sort((event1, event2) => event1.path.length - event2.path.length) - // We don't need to check for events.length - // because we know it has at least one element - callEventHandlerListeners(type._dEH, events, transaction) - } - }) - ) - fs.push(() => doc.emit('afterTransaction', [transaction, doc])) + transaction.changedParentTypes.forEach((events, type) => { + // We need to think about the possibility that the user transforms the + // Y.Doc in the event. + if (type._dEH.l.length > 0 && (type._item === null || !type._item.deleted)) { + events = events + .filter(event => + event.target._item === null || !event.target._item.deleted + ) + events + .forEach(event => { + event.currentTarget = type + // path is relative to the current target + event._path = null + }) + // sort events by path length so that top-level events are fired first. + events + .sort((event1, event2) => event1.path.length - event2.path.length) + // We don't need to check for events.length + // because we know it has at least one element + callEventHandlerListeners(type._dEH, events, transaction) + } + }) }) + fs.push(() => doc.emit('afterTransaction', [transaction, doc])) callAll(fs, []) + if (transaction._needFormattingCleanup && doc.cleanupFormatting) { + cleanupYTextAfterTransaction(transaction) + } } finally { // Replace deleted items with ItemDeleted / GC. // This is where content is actually remove from the Yjs Doc. if (doc.gc) { - tryGcDeleteSet(ds, store, doc.gcFilter) + tryGcDeleteSet(transaction, ds, doc.gcFilter) } - tryMergeDeleteSet(ds, store) + tryMerge(ds, store) // on all affected store.clients props, try to merge - transaction.afterState.forEach((clock, client) => { - const beforeClock = transaction.beforeState.get(client) || 0 - if (beforeClock !== clock) { - const structs = /** @type {Array} */ (store.clients.get(client)) - // we iterate from right to left so we can safely remove entries - const firstChangePos = math.max(findIndexSS(structs, beforeClock), 1) - for (let i = structs.length - 1; i >= firstChangePos; i--) { - tryToMergeWithLeft(structs, i) - } + transaction.insertSet.clients.forEach((ids, client) => { + const firstClock = ids.getIds()[0].clock + const structs = /** @type {Array} */ (store.clients.get(client)) + // we iterate from right to left so we can safely remove entries + const firstChangePos = math.max(findIndexSS(structs, firstClock), 1) + for (let i = structs.length - 1; i >= firstChangePos;) { + i -= 1 + tryToMergeWithLefts(structs, i) } }) // try to merge mergeStructs // @todo: it makes more sense to transform mergeStructs to a DS, sort it, and merge from right to left // but at the moment DS does not handle duplicates - for (let i = 0; i < mergeStructs.length; i++) { + for (let i = mergeStructs.length - 1; i >= 0; i--) { const { client, clock } = mergeStructs[i].id const structs = /** @type {Array} */ (store.clients.get(client)) const replacedStructPos = findIndexSS(structs, clock) if (replacedStructPos + 1 < structs.length) { - tryToMergeWithLeft(structs, replacedStructPos + 1) + if (tryToMergeWithLefts(structs, replacedStructPos + 1) > 1) { + continue // no need to perform next check, both are already merged + } } if (replacedStructPos > 0) { - tryToMergeWithLeft(structs, replacedStructPos) + tryToMergeWithLefts(structs, replacedStructPos) } } - if (!transaction.local && transaction.afterState.get(doc.clientID) !== transaction.beforeState.get(doc.clientID)) { + if (!transaction.local && transaction.insertSet.clients.has(doc.clientID)) { logging.print(logging.ORANGE, logging.BOLD, '[yjs] ', logging.UNBOLD, logging.RED, 'Changed the client-id because another client seems to be using it.') doc.clientID = generateNewClientId() } @@ -377,15 +442,21 @@ const cleanupTransactions = (transactionCleanups, i) => { /** * Implements the functionality of `y.transact(()=>{..})` * + * @template T * @param {Doc} doc - * @param {function(Transaction):void} f + * @param {function(Transaction):T} f * @param {any} [origin=true] + * @return {T} * * @function */ export const transact = (doc, f, origin = null, local = true) => { const transactionCleanups = doc._transactionCleanups let initialCall = false + /** + * @type {any} + */ + let result = null if (doc._transaction === null) { initialCall = true doc._transaction = new Transaction(doc, origin, local) @@ -396,18 +467,23 @@ export const transact = (doc, f, origin = null, local = true) => { doc.emit('beforeTransaction', [doc._transaction, doc]) } try { - f(doc._transaction) + result = f(doc._transaction) } finally { - if (initialCall && transactionCleanups[0] === doc._transaction) { - // The first transaction ended, now process observer calls. - // Observer call may create new transactions for which we need to call the observers and do cleanup. - // We don't want to nest these calls, so we execute these calls one after - // another. - // Also we need to ensure that all cleanups are called, even if the - // observes throw errors. - // This file is full of hacky try {} finally {} blocks to ensure that an - // event can throw errors and also that the cleanup is called. - cleanupTransactions(transactionCleanups, 0) + if (initialCall) { + const finishCleanup = doc._transaction === transactionCleanups[0] + doc._transaction = null + if (finishCleanup) { + // The first transaction ended, now process observer calls. + // Observer call may create new transactions for which we need to call the observers and do cleanup. + // We don't want to nest these calls, so we execute these calls one after + // another. + // Also we need to ensure that all cleanups are called, even if the + // observes throw errors. + // This file is full of hacky try {} finally {} blocks to ensure that an + // event can throw errors and also that the cleanup is called. + cleanupTransactions(transactionCleanups, 0) + } } } + return result } diff --git a/src/utils/UndoManager.js b/src/utils/UndoManager.js index fcfa9e0e9..6857eb2f5 100644 --- a/src/utils/UndoManager.js +++ b/src/utils/UndoManager.js @@ -1,6 +1,6 @@ import { - mergeDeleteSets, - iterateDeletedStructs, + mergeIdSets, + iterateStructsByIdSet, keepItem, transact, createID, @@ -8,19 +8,18 @@ import { isParentOf, followRedone, getItemCleanStart, - isDeleted, - addToDeleteSet, - Transaction, Doc, Item, GC, DeleteSet, AbstractType, YEvent // eslint-disable-line + YEvent, Transaction, Doc, Item, GC, IdSet, AbstractType // eslint-disable-line } from '../internals.js' import * as time from 'lib0/time' import * as array from 'lib0/array' -import { Observable } from 'lib0/observable' +import * as logging from 'lib0/logging' +import { ObservableV2 } from 'lib0/observable' -class StackItem { +export class StackItem { /** - * @param {DeleteSet} deletions - * @param {DeleteSet} insertions + * @param {IdSet} deletions + * @param {IdSet} insertions */ constructor (deletions, insertions) { this.insertions = insertions @@ -37,8 +36,8 @@ class StackItem { * @param {StackItem} stackItem */ const clearUndoManagerStackItem = (tr, um, stackItem) => { - iterateDeletedStructs(tr, stackItem.deletions, item => { - if (item instanceof Item && um.scope.some(type => isParentOf(type, item))) { + iterateStructsByIdSet(tr, stackItem.deletions, item => { + if (item instanceof Item && um.scope.some(type => type === tr.doc || isParentOf(/** @type {import('../utils/types.js').YType} */ (type), item))) { keepItem(item, false) } }) @@ -47,15 +46,10 @@ const clearUndoManagerStackItem = (tr, um, stackItem) => { /** * @param {UndoManager} undoManager * @param {Array} stack - * @param {string} eventType + * @param {'undo'|'redo'} eventType * @return {StackItem?} */ const popStackItem = (undoManager, stack, eventType) => { - /** - * Whether a change happened - * @type {StackItem?} - */ - let result = null /** * Keep a reference to the transaction so we can fire the event with the changedParentTypes * @type {any} @@ -64,7 +58,7 @@ const popStackItem = (undoManager, stack, eventType) => { const doc = undoManager.doc const scope = undoManager.scope transact(doc, transaction => { - while (stack.length > 0 && result === null) { + while (stack.length > 0 && undoManager.currStackItem === null) { const store = doc.store const stackItem = /** @type {StackItem} */ (stack.pop()) /** @@ -76,7 +70,7 @@ const popStackItem = (undoManager, stack, eventType) => { */ const itemsToDelete = [] let performedChange = false - iterateDeletedStructs(transaction, stackItem.insertions, struct => { + iterateStructsByIdSet(transaction, stackItem.insertions, struct => { if (struct instanceof Item) { if (struct.redone !== null) { let { item, diff } = followRedone(store, struct.id) @@ -85,23 +79,23 @@ const popStackItem = (undoManager, stack, eventType) => { } struct = item } - if (!struct.deleted && scope.some(type => isParentOf(type, /** @type {Item} */ (struct)))) { + if (!struct.deleted && scope.some(type => type === transaction.doc || isParentOf(/** @type {import('../utils/types.js').YType} */ (type), /** @type {Item} */ (struct)))) { itemsToDelete.push(struct) } } }) - iterateDeletedStructs(transaction, stackItem.deletions, struct => { + iterateStructsByIdSet(transaction, stackItem.deletions, struct => { if ( struct instanceof Item && - scope.some(type => isParentOf(type, struct)) && + scope.some(type => type === transaction.doc || isParentOf(/** @type {import('../utils/types.js').YType} */ (type), struct)) && // Never redo structs in stackItem.insertions because they were created and deleted in the same capture interval. - !isDeleted(stackItem.insertions, struct.id) + !stackItem.insertions.hasId(struct.id) ) { itemsToRedo.add(struct) } }) itemsToRedo.forEach(struct => { - performedChange = redoItem(transaction, struct, itemsToRedo, stackItem.insertions, undoManager.ignoreRemoteMapChanges) !== null || performedChange + performedChange = redoItem(transaction, struct, itemsToRedo, stackItem.insertions, undoManager.ignoreRemoteMapChanges, undoManager) !== null || performedChange }) // We want to delete in reverse order so that children are deleted before // parents, so we have more information available when items are filtered. @@ -112,7 +106,7 @@ const popStackItem = (undoManager, stack, eventType) => { performedChange = true } } - result = performedChange ? stackItem : null + undoManager.currStackItem = performedChange ? stackItem : null } transaction.changed.forEach((subProps, type) => { // destroy search marker if necessary @@ -122,11 +116,13 @@ const popStackItem = (undoManager, stack, eventType) => { }) _tr = transaction }, undoManager) - if (result != null) { + const res = undoManager.currStackItem + if (res != null) { const changedParentTypes = _tr.changedParentTypes - undoManager.emit('stack-item-popped', [{ stackItem: result, type: eventType, changedParentTypes }, undoManager]) + undoManager.emit('stack-item-popped', [{ stackItem: res, type: eventType, changedParentTypes, origin: undoManager }, undoManager]) + undoManager.currStackItem = null } - return result + return res } /** @@ -142,6 +138,14 @@ const popStackItem = (undoManager, stack, eventType) => { * @property {Doc} [doc] The document that this UndoManager operates on. Only needed if typeScope is empty. */ +/** + * @typedef {Object} StackItemEvent + * @property {StackItem} StackItemEvent.stackItem + * @property {any} StackItemEvent.origin + * @property {'undo'|'redo'} StackItemEvent.type + * @property {Map>>} StackItemEvent.changedParentTypes + */ + /** * Fires 'stack-item-added' event when a stack item was added to either the undo- or * the redo-stack. You may store additional stack information via the @@ -149,26 +153,27 @@ const popStackItem = (undoManager, stack, eventType) => { * Fires 'stack-item-popped' event when a stack item was popped from either the * undo- or the redo-stack. You may restore the saved stack information from `event.stackItem.meta`. * - * @extends {Observable<'stack-item-added'|'stack-item-popped'|'stack-cleared'|'stack-item-updated'>} + * @extends {ObservableV2<{'stack-item-added':function(StackItemEvent, UndoManager):void, 'stack-item-popped': function(StackItemEvent, UndoManager):void, 'stack-cleared': function({ undoStackCleared: boolean, redoStackCleared: boolean }):void, 'stack-item-updated': function(StackItemEvent, UndoManager):void }>} */ -export class UndoManager extends Observable { +export class UndoManager extends ObservableV2 { /** - * @param {AbstractType|Array>} typeScope Accepts either a single type, or an array of types + * @param {Doc|import('../utils/types.js').YType|Array} typeScope Limits the scope of the UndoManager. If this is set to a ydoc instance, all changes on that ydoc will be undone. If set to a specific type, only changes on that type or its children will be undone. Also accepts an array of types. * @param {UndoManagerOptions} options */ constructor (typeScope, { captureTimeout = 500, - captureTransaction = tr => true, + captureTransaction = _tr => true, deleteFilter = () => true, trackedOrigins = new Set([null]), ignoreRemoteMapChanges = false, - doc = /** @type {Doc} */ (array.isArray(typeScope) ? typeScope[0].doc : typeScope.doc) + doc = /** @type {Doc} */ (array.isArray(typeScope) ? typeScope[0].doc : typeScope instanceof Doc ? typeScope : typeScope.doc) } = {}) { super() /** - * @type {Array>} + * @type {Array} */ this.scope = [] + this.doc = doc this.addToScope(typeScope) this.deleteFilter = deleteFilter trackedOrigins.add(this) @@ -189,9 +194,15 @@ export class UndoManager extends Observable { */ this.undoing = false this.redoing = false - this.doc = doc + /** + * The currently popped stack item if UndoManager.undoing or UndoManager.redoing + * + * @type {StackItem|null} + */ + this.currStackItem = null this.lastChange = 0 this.ignoreRemoteMapChanges = ignoreRemoteMapChanges + this.captureTimeout = captureTimeout /** * @param {Transaction} transaction */ @@ -199,7 +210,7 @@ export class UndoManager extends Observable { // Only track certain transactions if ( !this.captureTransaction(transaction) || - !this.scope.some(type => transaction.changedParentTypes.has(type)) || + !this.scope.some(type => transaction.changedParentTypes.has(/** @type {import('../utils/types.js').YType} */ (type)) || type === this.doc) || (!this.trackedOrigins.has(transaction.origin) && (!transaction.origin || !this.trackedOrigins.has(transaction.origin.constructor))) ) { return @@ -213,21 +224,14 @@ export class UndoManager extends Observable { // neither undoing nor redoing: delete redoStack this.clear(false, true) } - const insertions = new DeleteSet() - transaction.afterState.forEach((endClock, client) => { - const startClock = transaction.beforeState.get(client) || 0 - const len = endClock - startClock - if (len > 0) { - addToDeleteSet(insertions, client, startClock, len) - } - }) + const insertions = transaction.insertSet const now = time.getUnixTime() let didAdd = false - if (now - this.lastChange < captureTimeout && stack.length > 0 && !undoing && !redoing) { + if (this.lastChange > 0 && now - this.lastChange < this.captureTimeout && stack.length > 0 && !undoing && !redoing) { // append change to last stack op const lastOp = stack[stack.length - 1] - lastOp.deletions = mergeDeleteSets([lastOp.deletions, transaction.deleteSet]) - lastOp.insertions = mergeDeleteSets([lastOp.insertions, insertions]) + lastOp.deletions = mergeIdSets([lastOp.deletions, transaction.deleteSet]) + lastOp.insertions = mergeIdSets([lastOp.insertions, insertions]) } else { // create a new stack op stack.push(new StackItem(transaction.deleteSet, insertions)) @@ -237,11 +241,14 @@ export class UndoManager extends Observable { this.lastChange = now } // make sure that deleted structs are not gc'd - iterateDeletedStructs(transaction, transaction.deleteSet, /** @param {Item|GC} item */ item => { - if (item instanceof Item && this.scope.some(type => isParentOf(type, item))) { + iterateStructsByIdSet(transaction, transaction.deleteSet, /** @param {Item|GC} item */ item => { + if (item instanceof Item && this.scope.some(type => type === transaction.doc || isParentOf(/** @type {import('../utils/types.js').YType} */ (type), item))) { keepItem(item, true) } }) + /** + * @type {[StackItemEvent, UndoManager]} + */ const changeEvent = [{ stackItem: stack[stack.length - 1], origin: transaction.origin, type: undoing ? 'redo' : 'undo', changedParentTypes: transaction.changedParentTypes }, this] if (didAdd) { this.emit('stack-item-added', changeEvent) @@ -256,12 +263,17 @@ export class UndoManager extends Observable { } /** - * @param {Array> | AbstractType} ytypes + * Extend the scope. + * + * @param {Array | import('../utils/types.js').YType | Doc} ytypes */ addToScope (ytypes) { + const tmpSet = new Set(this.scope) ytypes = array.isArray(ytypes) ? ytypes : [ytypes] ytypes.forEach(ytype => { - if (this.scope.every(yt => yt !== ytype)) { + if (!tmpSet.has(ytype)) { + tmpSet.add(ytype) + if (ytype instanceof AbstractType ? ytype.doc !== this.doc : ytype !== this.doc) logging.warn('[yjs#509] Not same Y.Doc') // use MultiDocUndoManager instead. also see https://github.com/yjs/yjs/issues/509 this.scope.push(ytype) } }) diff --git a/src/utils/UpdateEncoder.js b/src/utils/UpdateEncoder.js index e8c5d06c8..e23b9d020 100644 --- a/src/utils/UpdateEncoder.js +++ b/src/utils/UpdateEncoder.js @@ -1,4 +1,3 @@ - import * as error from 'lib0/error' import * as encoding from 'lib0/encoding' @@ -6,7 +5,7 @@ import { ID // eslint-disable-line } from '../internals.js' -export class DSEncoderV1 { +export class IdSetEncoderV1 { constructor () { this.restEncoder = encoding.createEncoder() } @@ -15,26 +14,26 @@ export class DSEncoderV1 { return encoding.toUint8Array(this.restEncoder) } - resetDsCurVal () { + resetIdSetCurVal () { // nop } /** * @param {number} clock */ - writeDsClock (clock) { + writeIdSetClock (clock) { encoding.writeVarUint(this.restEncoder, clock) } /** * @param {number} len */ - writeDsLen (len) { + writeIdSetLen (len) { encoding.writeVarUint(this.restEncoder, len) } } -export class UpdateEncoderV1 extends DSEncoderV1 { +export class UpdateEncoderV1 extends IdSetEncoderV1 { /** * @param {ID} id */ @@ -125,7 +124,7 @@ export class UpdateEncoderV1 extends DSEncoderV1 { } } -export class DSEncoderV2 { +export class IdSetEncoderV2 { constructor () { this.restEncoder = encoding.createEncoder() // encodes all the rest / non-optimized this.dsCurrVal = 0 @@ -135,14 +134,14 @@ export class DSEncoderV2 { return encoding.toUint8Array(this.restEncoder) } - resetDsCurVal () { + resetIdSetCurVal () { this.dsCurrVal = 0 } /** * @param {number} clock */ - writeDsClock (clock) { + writeIdSetClock (clock) { const diff = clock - this.dsCurrVal this.dsCurrVal = clock encoding.writeVarUint(this.restEncoder, diff) @@ -151,7 +150,7 @@ export class DSEncoderV2 { /** * @param {number} len */ - writeDsLen (len) { + writeIdSetLen (len) { if (len === 0) { error.unexpectedCase() } @@ -160,7 +159,7 @@ export class DSEncoderV2 { } } -export class UpdateEncoderV2 extends DSEncoderV2 { +export class UpdateEncoderV2 extends IdSetEncoderV2 { constructor () { super() /** @@ -168,7 +167,7 @@ export class UpdateEncoderV2 extends DSEncoderV2 { */ this.keyMap = new Map() /** - * Refers to the next uniqe key-identifier to me used. + * Refers to the next unique key-identifier to me used. * See writeKey method for more information. * * @type {number} diff --git a/src/utils/YEvent.js b/src/utils/YEvent.js index 5d33ef676..e209e80cc 100644 --- a/src/utils/YEvent.js +++ b/src/utils/YEvent.js @@ -1,30 +1,35 @@ - import { - isDeleted, - Item, AbstractType, Transaction, AbstractStruct // eslint-disable-line + diffIdSet, + mergeIdSets, + noAttributionsManager, + AbstractAttributionManager, Item, AbstractType, Transaction, AbstractStruct // eslint-disable-line } from '../internals.js' -import * as set from 'lib0/set' -import * as array from 'lib0/array' +import * as delta from 'lib0/delta' // eslint-disable-line /** - * @template {AbstractType} T + * @typedef {import('./types.js').YType} _YType + */ + +/** + * @template {AbstractType} Target * YEvent describes the changes on a YType. */ export class YEvent { /** - * @param {T} target The changed type. + * @param {Target} target The changed type. * @param {Transaction} transaction + * @param {Set?} subs The keys that changed */ - constructor (target, transaction) { + constructor (target, transaction, subs) { /** * The type on which this event was created on. - * @type {T} + * @type {Target} */ this.target = target /** * The current target on which the observe callback is called. - * @type {AbstractType} + * @type {_YType} */ this.currentTarget = target /** @@ -33,17 +38,35 @@ export class YEvent { */ this.transaction = transaction /** - * @type {Object|null} + * @type {(Target extends AbstractType ? D : delta.Delta)|null} */ - this._changes = null + this._delta = null /** - * @type {null | Map} + * @type {(Target extends AbstractType ? import('../internals.js').ToDeepEventDelta : delta.Delta)|null} */ - this._keys = null + this._deltaDeep = null /** - * @type {null | Array<{ insert?: string | Array | object | AbstractType, retain?: number, delete?: number, attributes?: Object }>} + * @type {Array|null} */ - this._delta = null + this._path = null + /** + * Whether the children changed. + * @type {Boolean} + * @private + */ + this.childListChanged = false + /** + * Set of all changed attributes. + * @type {Set} + */ + this.keysChanged = new Set() + subs?.forEach((sub) => { + if (sub === null) { + this.childListChanged = true + } else { + this.keysChanged.add(sub) + } + }) } /** @@ -60,8 +83,7 @@ export class YEvent { * type === event.target // => true */ get path () { - // @ts-ignore _item is defined because target is integrated - return getPathTo(this.currentTarget, this.target) + return this._path || (this._path = getPathTo(this.currentTarget, this.target)) } /** @@ -73,145 +95,56 @@ export class YEvent { * @return {boolean} */ deletes (struct) { - return isDeleted(this.transaction.deleteSet, struct.id) + return this.transaction.deleteSet.hasId(struct.id) } /** - * @type {Map} + * Check if a struct is added by this event. + * + * In contrast to change.deleted, this method also returns true if the struct was added and then deleted. + * + * @param {AbstractStruct} struct + * @return {boolean} */ - get keys () { - if (this._keys === null) { - const keys = new Map() - const target = this.target - const changed = /** @type Set */ (this.transaction.changed.get(target)) - changed.forEach(key => { - if (key !== null) { - const item = /** @type {Item} */ (target._map.get(key)) - /** - * @type {'delete' | 'add' | 'update'} - */ - let action - let oldValue - if (this.adds(item)) { - let prev = item.left - while (prev !== null && this.adds(prev)) { - prev = prev.left - } - if (this.deletes(item)) { - if (prev !== null && this.deletes(prev)) { - action = 'delete' - oldValue = array.last(prev.content.getContent()) - } else { - return - } - } else { - if (prev !== null && this.deletes(prev)) { - action = 'update' - oldValue = array.last(prev.content.getContent()) - } else { - action = 'add' - oldValue = undefined - } - } - } else { - if (this.deletes(item)) { - action = 'delete' - oldValue = array.last(/** @type {Item} */ item.content.getContent()) - } else { - return // nop - } - } - keys.set(key, { action, oldValue }) - } - }) - this._keys = keys - } - return this._keys + adds (struct) { + return this.transaction.insertSet.hasId(struct.id) } /** - * @type {Array<{insert?: string | Array | object | AbstractType, retain?: number, delete?: number, attributes?: Object}>} + * @template {boolean} [Deep=false] + * @param {AbstractAttributionManager} am + * @param {object} [opts] + * @param {Deep} [opts.deep] + * @return {Target extends AbstractType ? (Deep extends true ? import('../internals.js').ToDeepEventDelta : D) : delta.Delta} The Delta representation of this type. + * + * @public */ - get delta () { - return this.changes.delta + getDelta (am = noAttributionsManager, { deep } = {}) { + const itemsToRender = mergeIdSets([diffIdSet(this.transaction.insertSet, this.transaction.deleteSet), diffIdSet(this.transaction.deleteSet, this.transaction.insertSet)]) + const modified = deep ? this.transaction.changedParentTypes : null + return /** @type {any} */ (this.target.getContent(am, { itemsToRender, retainDeletes: true, renderAttrs: this.keysChanged, renderChildren: deep || this.childListChanged, deletedItems: this.transaction.deleteSet, deep: !!deep, modified })) } /** - * Check if a struct is added by this event. + * Compute the changes in the delta format. + * A {@link https://quilljs.com/docs/delta/|Quill Delta}) that represents the changes on the document. * - * In contrast to change.deleted, this method also returns true if the struct was added and then deleted. - * - * @param {AbstractStruct} struct - * @return {boolean} + * @type {Target extends AbstractType ? D : delta.Delta} The Delta representation of this type. + * @public */ - adds (struct) { - return struct.id.clock >= (this.transaction.beforeState.get(struct.id.client) || 0) + get delta () { + return /** @type {any} */ (this._delta ?? (this._delta = this.getDelta())) } /** - * @type {{added:Set,deleted:Set,keys:Map,delta:Array<{insert?:Array|string, delete?:number, retain?:number}>}} + * Compute the changes in the delta format. + * A {@link https://quilljs.com/docs/delta/|Quill Delta}) that represents the changes on the document. + * + * @type {Target extends AbstractType ? D : delta.Delta} The Delta representation of this type. + * @public */ - get changes () { - let changes = this._changes - if (changes === null) { - const target = this.target - const added = set.create() - const deleted = set.create() - /** - * @type {Array<{insert:Array}|{delete:number}|{retain:number}>} - */ - const delta = [] - changes = { - added, - deleted, - delta, - keys: this.keys - } - const changed = /** @type Set */ (this.transaction.changed.get(target)) - if (changed.has(null)) { - /** - * @type {any} - */ - let lastOp = null - const packOp = () => { - if (lastOp) { - delta.push(lastOp) - } - } - for (let item = target._start; item !== null; item = item.right) { - if (item.deleted) { - if (this.deletes(item) && !this.adds(item)) { - if (lastOp === null || lastOp.delete === undefined) { - packOp() - lastOp = { delete: 0 } - } - lastOp.delete += item.length - deleted.add(item) - } // else nop - } else { - if (this.adds(item)) { - if (lastOp === null || lastOp.insert === undefined) { - packOp() - lastOp = { insert: [] } - } - lastOp.insert = lastOp.insert.concat(item.content.getContent()) - added.add(item) - } else { - if (lastOp === null || lastOp.retain === undefined) { - packOp() - lastOp = { retain: 0 } - } - lastOp.retain += item.length - } - } - } - if (lastOp !== null && lastOp.retain === undefined) { - packOp() - } - } - this._changes = changes - } - return /** @type {any} */ (changes) + get deltaDeep () { + return /** @type {any} */ (this._deltaDeep ?? (this._deltaDeep = this.getDelta(noAttributionsManager, { deep: true }))) } } @@ -225,8 +158,8 @@ export class YEvent { * console.log(path) // might look like => [2, 'key1'] * child === type.get(path[0]).get(path[1]) * - * @param {AbstractType} parent - * @param {AbstractType} child target + * @param {_YType} parent + * @param {_YType} child target * @return {Array} Path to the target * * @private @@ -241,16 +174,16 @@ const getPathTo = (parent, child) => { } else { // parent is array-ish let i = 0 - let c = /** @type {AbstractType} */ (child._item.parent)._start + let c = /** @type {import('../utils/types.js').YType} */ (child._item.parent)._start while (c !== child._item && c !== null) { - if (!c.deleted) { - i++ + if (!c.deleted && c.countable) { + i += c.length } c = c.right } path.unshift(i) } - child = /** @type {AbstractType} */ (child._item.parent) + child = /** @type {_YType} */ (child._item.parent) } return path } diff --git a/src/utils/encoding.js b/src/utils/encoding.js index 29af8bc63..d8bf4e878 100644 --- a/src/utils/encoding.js +++ b/src/utils/encoding.js @@ -1,4 +1,3 @@ - /** * @module encoding */ @@ -18,56 +17,91 @@ import { findIndexSS, getState, - createID, getStateVector, readAndApplyDeleteSet, - writeDeleteSet, - createDeleteSetFromStructStore, + writeIdSet, transact, - readItemContent, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, - DSEncoderV2, + IdSetEncoderV2, DSDecoderV1, - DSEncoderV1, + IdSetEncoderV1, mergeUpdates, mergeUpdatesV2, Skip, diffUpdateV2, convertUpdateFormatV2ToV1, - DSDecoderV2, Doc, Transaction, GC, Item, StructStore // eslint-disable-line + readStructSet, + removeRangesFromStructSet, + createIdSet, + StructSet, IdSet, DSDecoderV2, Doc, Transaction, GC, Item, StructStore, // eslint-disable-line + createID, + IdRange } from '../internals.js' import * as encoding from 'lib0/encoding' import * as decoding from 'lib0/decoding' -import * as binary from 'lib0/binary' import * as map from 'lib0/map' import * as math from 'lib0/math' +import * as array from 'lib0/array' /** * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder * @param {Array} structs All structs by `client` * @param {number} client - * @param {number} clock write structs starting with `ID(client,clock)` + * @param {Array} idranges * * @function */ -const writeStructs = (encoder, structs, client, clock) => { - // write first id - clock = math.max(clock, structs[0].id.clock) // make sure the first id exists - const startNewStructs = findIndexSS(structs, clock) +const writeStructs = (encoder, structs, client, idranges) => { + let structsToWrite = 0 // this accounts for the skips + /** + * @type {Array<{ start: number, end: number, startClock: number, endClock: number }>} + */ + const indexRanges = [] + const firstPossibleClock = structs[0].id.clock + const lastStruct = array.last(structs) + const lastPossibleClock = lastStruct.id.clock + lastStruct.length + idranges.forEach(idrange => { + const startClock = math.max(idrange.clock, firstPossibleClock) + const endClock = math.min(idrange.clock + idrange.len, lastPossibleClock) + if (startClock >= endClock) return // structs for this range do not exist + // inclusive start + const start = findIndexSS(structs, startClock) + // exclusive end + const end = findIndexSS(structs, endClock - 1) + 1 + structsToWrite += end - start + indexRanges.push({ + start, + end, + startClock, + endClock + }) + }) + structsToWrite += idranges.length - 1 + // start writing with this clock. this is updated to the next clock that we expect to write + let clock = indexRanges[0].startClock // write # encoded structs - encoding.writeVarUint(encoder.restEncoder, structs.length - startNewStructs) + encoding.writeVarUint(encoder.restEncoder, structsToWrite) encoder.writeClient(client) + // write clock encoding.writeVarUint(encoder.restEncoder, clock) - const firstStruct = structs[startNewStructs] - // write first struct with an offset - firstStruct.write(encoder, clock - firstStruct.id.clock) - for (let i = startNewStructs + 1; i < structs.length; i++) { - structs[i].write(encoder, 0) - } + indexRanges.forEach(indexRange => { + const skipLen = indexRange.startClock - clock + if (skipLen > 0) { + new Skip(createID(client, clock), skipLen).write(encoder, 0) + clock += skipLen + } + for (let i = indexRange.start; i < indexRange.end; i++) { + const struct = structs[i] + const structEnd = struct.id.clock + struct.length + const offsetEnd = math.max(structEnd - indexRange.endClock, 0) + struct.write(encoder, clock - struct.id.clock, offsetEnd) + clock = structEnd - offsetEnd + } + }) } /** @@ -87,7 +121,7 @@ export const writeClientsStructs = (encoder, store, _sm) => { sm.set(client, clock) } }) - getStateVector(store).forEach((clock, client) => { + getStateVector(store).forEach((_clock, client) => { if (!_sm.has(client)) { sm.set(client, 0) } @@ -96,106 +130,33 @@ export const writeClientsStructs = (encoder, store, _sm) => { encoding.writeVarUint(encoder.restEncoder, sm.size) // Write items with higher client ids first // This heavily improves the conflict algorithm. - Array.from(sm.entries()).sort((a, b) => b[0] - a[0]).forEach(([client, clock]) => { - // @ts-ignore - writeStructs(encoder, store.clients.get(client), client, clock) + array.from(sm.entries()).sort((a, b) => b[0] - a[0]).forEach(([client, clock]) => { + const structs = /** @type {Array} */ (store.clients.get(client)) + const lastStruct = structs[structs.length - 1] + writeStructs(encoder, structs, client, [new IdRange(clock, lastStruct.id.clock + lastStruct.length - clock)]) }) } /** - * @param {UpdateDecoderV1 | UpdateDecoderV2} decoder The decoder object to read data from. - * @param {Doc} doc - * @return {Map }>} + * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder + * @param {StructStore} store + * @param {IdSet} idset + * + * @todo at the moment this writes the full deleteset range * * @private * @function */ -export const readClientsStructRefs = (decoder, doc) => { - /** - * @type {Map }>} - */ - const clientRefs = map.create() - const numOfStateUpdates = decoding.readVarUint(decoder.restDecoder) - for (let i = 0; i < numOfStateUpdates; i++) { - const numberOfStructs = decoding.readVarUint(decoder.restDecoder) - /** - * @type {Array} - */ - const refs = new Array(numberOfStructs) - const client = decoder.readClient() - let clock = decoding.readVarUint(decoder.restDecoder) - // const start = performance.now() - clientRefs.set(client, { i: 0, refs }) - for (let i = 0; i < numberOfStructs; i++) { - const info = decoder.readInfo() - switch (binary.BITS5 & info) { - case 0: { // GC - const len = decoder.readLen() - refs[i] = new GC(createID(client, clock), len) - clock += len - break - } - case 10: { // Skip Struct (nothing to apply) - // @todo we could reduce the amount of checks by adding Skip struct to clientRefs so we know that something is missing. - const len = decoding.readVarUint(decoder.restDecoder) - refs[i] = new Skip(createID(client, clock), len) - clock += len - break - } - default: { // Item with content - /** - * The optimized implementation doesn't use any variables because inlining variables is faster. - * Below a non-optimized version is shown that implements the basic algorithm with - * a few comments - */ - const cantCopyParentInfo = (info & (binary.BIT7 | binary.BIT8)) === 0 - // If parent = null and neither left nor right are defined, then we know that `parent` is child of `y` - // and we read the next string as parentYKey. - // It indicates how we store/retrieve parent from `y.share` - // @type {string|null} - const struct = new Item( - createID(client, clock), - null, // leftd - (info & binary.BIT8) === binary.BIT8 ? decoder.readLeftID() : null, // origin - null, // right - (info & binary.BIT7) === binary.BIT7 ? decoder.readRightID() : null, // right origin - cantCopyParentInfo ? (decoder.readParentInfo() ? doc.get(decoder.readString()) : decoder.readLeftID()) : null, // parent - cantCopyParentInfo && (info & binary.BIT6) === binary.BIT6 ? decoder.readString() : null, // parentSub - readItemContent(decoder, info) // item content - ) - /* A non-optimized implementation of the above algorithm: - - // The item that was originally to the left of this item. - const origin = (info & binary.BIT8) === binary.BIT8 ? decoder.readLeftID() : null - // The item that was originally to the right of this item. - const rightOrigin = (info & binary.BIT7) === binary.BIT7 ? decoder.readRightID() : null - const cantCopyParentInfo = (info & (binary.BIT7 | binary.BIT8)) === 0 - const hasParentYKey = cantCopyParentInfo ? decoder.readParentInfo() : false - // If parent = null and neither left nor right are defined, then we know that `parent` is child of `y` - // and we read the next string as parentYKey. - // It indicates how we store/retrieve parent from `y.share` - // @type {string|null} - const parentYKey = cantCopyParentInfo && hasParentYKey ? decoder.readString() : null - - const struct = new Item( - createID(client, clock), - null, // leftd - origin, // origin - null, // right - rightOrigin, // right origin - cantCopyParentInfo && !hasParentYKey ? decoder.readLeftID() : (parentYKey !== null ? doc.get(parentYKey) : null), // parent - cantCopyParentInfo && (info & binary.BIT6) === binary.BIT6 ? decoder.readString() : null, // parentSub - readItemContent(decoder, info) // item content - ) - */ - refs[i] = struct - clock += struct.length - } - } - } - // console.log('time to read: ', performance.now() - start) // @todo remove - } - return clientRefs +export const writeStructsFromIdSet = (encoder, store, idset) => { + // write # states that were updated + encoding.writeVarUint(encoder.restEncoder, idset.clients.size) + // Write items with higher client ids first + // This heavily improves the conflict algorithm. + array.from(idset.clients.entries()).sort((a, b) => b[0] - a[0]).forEach(([client, ids]) => { + const idRanges = ids.getIds() + const structs = /** @type {Array} */ (store.clients.get(client)) + writeStructs(encoder, structs, client, idRanges) + }) } /** @@ -212,14 +173,14 @@ export const readClientsStructRefs = (decoder, doc) => { * then we start emptying the stack. * * It is not possible to have circles: i.e. struct1 (from client1) depends on struct2 (from client2) - * depends on struct3 (from client1). Therefore the max stack size is eqaul to `structReaders.length`. + * depends on struct3 (from client1). Therefore the max stack size is equal to `structReaders.length`. * * This method is implemented in a way so that we can resume computation if this update * causally depends on another update. * * @param {Transaction} transaction * @param {StructStore} store - * @param {Map} clientsStructRefs + * @param {StructSet} clientsStructRefs * @return { null | { update: Uint8Array, missing: Map } } * * @private @@ -231,7 +192,7 @@ const integrateStructs = (transaction, store, clientsStructRefs) => { */ const stack = [] // sort them so that we take the higher id first, in case of conflicts the lower id will probably not conflict with the id from the higher user. - let clientsStructRefsIds = Array.from(clientsStructRefs.keys()).sort((a, b) => a - b) + let clientsStructRefsIds = array.from(clientsStructRefs.clients.keys()).sort((a, b) => a - b) if (clientsStructRefsIds.length === 0) { return null } @@ -239,11 +200,11 @@ const integrateStructs = (transaction, store, clientsStructRefs) => { if (clientsStructRefsIds.length === 0) { return null } - let nextStructsTarget = /** @type {{i:number,refs:Array}} */ (clientsStructRefs.get(clientsStructRefsIds[clientsStructRefsIds.length - 1])) + let nextStructsTarget = /** @type {{i:number,refs:Array}} */ (clientsStructRefs.clients.get(clientsStructRefsIds[clientsStructRefsIds.length - 1])) while (nextStructsTarget.refs.length === nextStructsTarget.i) { clientsStructRefsIds.pop() if (clientsStructRefsIds.length > 0) { - nextStructsTarget = /** @type {{i:number,refs:Array}} */ (clientsStructRefs.get(clientsStructRefsIds[clientsStructRefsIds.length - 1])) + nextStructsTarget = /** @type {{i:number,refs:Array}} */ (clientsStructRefs.clients.get(clientsStructRefsIds[clientsStructRefsIds.length - 1])) } else { return null } @@ -251,7 +212,7 @@ const integrateStructs = (transaction, store, clientsStructRefs) => { return nextStructsTarget } let curStructsTarget = getNextStructTarget() - if (curStructsTarget === null && stack.length === 0) { + if (curStructsTarget === null) { return null } @@ -277,17 +238,23 @@ const integrateStructs = (transaction, store, clientsStructRefs) => { // caching the state because it is used very often const state = new Map() + // // caching the state because it is used very often + // const currentInsertSet = createIdSet() + // clientsStructRefsIds.forEach(clientId => { + // currentInsertSet.clients.set(clientid, new IdRanges(_createInsertSliceFromStructs(store.clients.get(clientId) ?? [], false))) + // }) + const addStackToRestSS = () => { for (const item of stack) { const client = item.id.client - const unapplicableItems = clientsStructRefs.get(client) - if (unapplicableItems) { + const inapplicableItems = clientsStructRefs.clients.get(client) + if (inapplicableItems) { // decrement because we weren't able to apply previous operation - unapplicableItems.i-- - restStructs.clients.set(client, unapplicableItems.refs.slice(unapplicableItems.i)) - clientsStructRefs.delete(client) - unapplicableItems.i = 0 - unapplicableItems.refs = [] + inapplicableItems.i-- + restStructs.clients.set(client, inapplicableItems.refs.slice(inapplicableItems.i)) + clientsStructRefs.clients.delete(client) + inapplicableItems.i = 0 + inapplicableItems.refs = [] } else { // item was the last item on clientsStructRefs and the field was already cleared. Add item to restStructs and continue restStructs.clients.set(client, [item]) @@ -303,34 +270,31 @@ const integrateStructs = (transaction, store, clientsStructRefs) => { if (stackHead.constructor !== Skip) { const localClock = map.setIfUndefined(state, stackHead.id.client, () => getState(store, stackHead.id.client)) const offset = localClock - stackHead.id.clock - if (offset < 0) { - // update from the same client is missing + const missing = stackHead.getMissing(transaction, store) + if (missing !== null) { stack.push(stackHead) - updateMissingSv(stackHead.id.client, stackHead.id.clock - 1) - // hid a dead wall, add all items from stack to restSS - addStackToRestSS() + // get the struct reader that has the missing struct + /** + * @type {{ refs: Array, i: number }} + */ + const structRefs = clientsStructRefs.clients.get(/** @type {number} */ (missing)) || { refs: [], i: 0 } + if (structRefs.refs.length === structRefs.i || missing === stackHead.id.client || stack.some(s => s.id.client === missing)) { // @todo this could be optimized! + // This update message causally depends on another update message that doesn't exist yet + updateMissingSv(/** @type {number} */ (missing), getState(store, missing)) + addStackToRestSS() + } else { + stackHead = structRefs.refs[structRefs.i++] + continue + } } else { - const missing = stackHead.getMissing(transaction, store) - if (missing !== null) { - stack.push(stackHead) - // get the struct reader that has the missing struct - /** - * @type {{ refs: Array, i: number }} - */ - const structRefs = clientsStructRefs.get(/** @type {number} */ (missing)) || { refs: [], i: 0 } - if (structRefs.refs.length === structRefs.i) { - // This update message causally depends on another update message that doesn't exist yet - updateMissingSv(/** @type {number} */ (missing), getState(store, missing)) - addStackToRestSS() - } else { - stackHead = structRefs.refs[structRefs.i++] - continue - } - } else if (offset === 0 || offset < stackHead.length) { - // all fine, apply the stackhead - stackHead.integrate(transaction, offset) - state.set(stackHead.id.client, stackHead.id.clock + stackHead.length) + // all fine, apply the stackhead + // but first add a skip to structs if necessary + if (offset < 0) { + const skip = new Skip(createID(stackHead.id.client, localClock), -offset) + skip.integrate(transaction, 0) } + stackHead.integrate(transaction, 0) + state.set(stackHead.id.client, math.max(stackHead.id.clock + stackHead.length, localClock)) } } // iterate to next stackHead @@ -366,12 +330,12 @@ const integrateStructs = (transaction, store, clientsStructRefs) => { * @private * @function */ -export const writeStructsFromTransaction = (encoder, transaction) => writeClientsStructs(encoder, transaction.doc.store, transaction.beforeState) +export const writeStructsFromTransaction = (encoder, transaction) => writeStructsFromIdSet(encoder, transaction.doc.store, transaction.insertSet) /** * Read and apply a document update. * - * This function has the same effect as `applyUpdate` but accepts an decoder. + * This function has the same effect as `applyUpdate` but accepts a decoder. * * @param {decoding.Decoder} decoder * @param {Doc} ydoc @@ -388,7 +352,21 @@ export const readUpdateV2 = (decoder, ydoc, transactionOrigin, structDecoder = n const doc = transaction.doc const store = doc.store // let start = performance.now() - const ss = readClientsStructRefs(structDecoder, doc) + const ss = readStructSet(structDecoder, doc) + const knownState = createIdSet() + ss.clients.forEach((_, client) => { + const storeStructs = store.clients.get(client) + if (storeStructs) { + const last = storeStructs[storeStructs.length - 1] + knownState.add(client, 0, last.id.clock + last.length) + // remove known items from ss + store.skips.clients.get(client)?.getIds().forEach(idrange => { + knownState.delete(client, idrange.clock, idrange.len) + }) + } + }) + // remove known items from ss + removeRangesFromStructSet(ss, knownState) // console.log('time to read structs: ', performance.now() - start) // @todo remove // start = performance.now() // console.log('time to merge: ', performance.now() - start) // @todo remove @@ -398,7 +376,7 @@ export const readUpdateV2 = (decoder, ydoc, transactionOrigin, structDecoder = n if (pending) { // check if we can apply something for (const [client, clock] of pending.missing) { - if (clock < getState(store, client)) { + if (ss.clients.has(client) || clock < getState(store, client)) { retry = true break } @@ -452,7 +430,7 @@ export const readUpdateV2 = (decoder, ydoc, transactionOrigin, structDecoder = n /** * Read and apply a document update. * - * This function has the same effect as `applyUpdate` but accepts an decoder. + * This function has the same effect as `applyUpdate` but accepts a decoder. * * @param {decoding.Decoder} decoder * @param {Doc} ydoc @@ -504,7 +482,7 @@ export const applyUpdate = (ydoc, update, transactionOrigin) => applyUpdateV2(yd */ export const writeStateAsUpdate = (encoder, doc, targetStateVector = new Map()) => { writeClientsStructs(encoder, doc.store, targetStateVector) - writeDeleteSet(encoder, createDeleteSetFromStructStore(doc.store)) + writeIdSet(encoder, doc.store.ds) } /** @@ -523,6 +501,9 @@ export const writeStateAsUpdate = (encoder, doc, targetStateVector = new Map()) export const encodeStateAsUpdateV2 = (doc, encodedTargetStateVector = new Uint8Array([0]), encoder = new UpdateEncoderV2()) => { const targetStateVector = decodeStateVector(encodedTargetStateVector) writeStateAsUpdate(encoder, doc, targetStateVector) + /** + * @type {Uint8Array[]} + */ const updates = [encoder.toUint8Array()] // also add the pending updates (if there are any) if (doc.store.pendingDs) { @@ -595,13 +576,13 @@ export const readStateVector = decoder => { export const decodeStateVector = decodedState => readStateVector(new DSDecoderV1(decoding.createDecoder(decodedState))) /** - * @param {DSEncoderV1 | DSEncoderV2} encoder + * @param {IdSetEncoderV1 | IdSetEncoderV2} encoder * @param {Map} sv * @function */ export const writeStateVector = (encoder, sv) => { encoding.writeVarUint(encoder.restEncoder, sv.size) - Array.from(sv.entries()).sort((a, b) => b[0] - a[0]).forEach(([client, clock]) => { + array.from(sv.entries()).sort((a, b) => b[0] - a[0]).forEach(([client, clock]) => { encoding.writeVarUint(encoder.restEncoder, client) // @todo use a special client decoder that is based on mapping encoding.writeVarUint(encoder.restEncoder, clock) }) @@ -609,7 +590,7 @@ export const writeStateVector = (encoder, sv) => { } /** - * @param {DSEncoderV1 | DSEncoderV2} encoder + * @param {IdSetEncoderV1 | IdSetEncoderV2} encoder * @param {Doc} doc * * @function @@ -620,12 +601,12 @@ export const writeDocumentStateVector = (encoder, doc) => writeStateVector(encod * Encode State as Uint8Array. * * @param {Doc|Map} doc - * @param {DSEncoderV1 | DSEncoderV2} [encoder] + * @param {IdSetEncoderV1 | IdSetEncoderV2} [encoder] * @return {Uint8Array} * * @function */ -export const encodeStateVectorV2 = (doc, encoder = new DSEncoderV2()) => { +export const encodeStateVectorV2 = (doc, encoder = new IdSetEncoderV2()) => { if (doc instanceof Map) { writeStateVector(encoder, doc) } else { @@ -642,4 +623,4 @@ export const encodeStateVectorV2 = (doc, encoder = new DSEncoderV2()) => { * * @function */ -export const encodeStateVector = doc => encodeStateVectorV2(doc, new DSEncoderV1()) +export const encodeStateVector = doc => encodeStateVectorV2(doc, new IdSetEncoderV1()) diff --git a/src/utils/isParentOf.js b/src/utils/isParentOf.js index d3012e24d..e26d4eb10 100644 --- a/src/utils/isParentOf.js +++ b/src/utils/isParentOf.js @@ -1,10 +1,9 @@ - import { AbstractType, Item } from '../internals.js' // eslint-disable-line /** * Check if `parent` is a parent of `child`. * - * @param {AbstractType} parent + * @param {import('../utils/types.js').YType} parent * @param {Item|null} child * @return {Boolean} Whether `parent` is a parent of `child`. * diff --git a/src/utils/logging.js b/src/utils/logging.js index 377097638..989ac4881 100644 --- a/src/utils/logging.js +++ b/src/utils/logging.js @@ -1,4 +1,3 @@ - import { AbstractType // eslint-disable-line } from '../internals.js' diff --git a/src/utils/types.js b/src/utils/types.js new file mode 100644 index 000000000..f2cc7747c --- /dev/null +++ b/src/utils/types.js @@ -0,0 +1,21 @@ +/** + * @typedef {import('../types/YArray.js').YArray + * | import('../types/YMap.js').YMap + * | import('../types/YText.js').YText + * | import('../types/YXmlFragment.js').YXmlFragment + * | import('../types/YXmlElement.js').YXmlElement + * | import('../types/YXmlHook.js').YXmlHook + * | import('../types/YXmlText.js').YXmlText} YValueType + */ + +/** + * @typedef {Object|Array|number|null|string|Uint8Array|BigInt|YValueType} YValue + */ + +/** + * @typedef {import('../types/AbstractType.js').AbstractType} YType + */ + +/** + * @typedef {typeof import('../types/AbstractType.js').AbstractType} YTypeConstructors + */ diff --git a/src/utils/updates.js b/src/utils/updates.js index 950f1413b..ba6ef59d2 100644 --- a/src/utils/updates.js +++ b/src/utils/updates.js @@ -1,20 +1,40 @@ - import * as binary from 'lib0/binary' import * as decoding from 'lib0/decoding' import * as encoding from 'lib0/encoding' +import * as error from 'lib0/error' +import * as f from 'lib0/function' import * as logging from 'lib0/logging' +import * as map from 'lib0/map' import * as math from 'lib0/math' +import * as string from 'lib0/string' + import { + ContentAny, + ContentBinary, + ContentDeleted, + ContentDoc, + ContentEmbed, + ContentFormat, + ContentJSON, + ContentString, + ContentType, createID, + decodeStateVector, + IdSetEncoderV1, + IdSetEncoderV2, + GC, + Item, + mergeIdSets, + readIdSet, readItemContent, - readDeleteSet, - writeDeleteSet, Skip, - mergeDeleteSets, - DSEncoderV1, - DSEncoderV2, - decodeStateVector, - Item, GC, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2 // eslint-disable-line + UpdateDecoderV1, + UpdateDecoderV2, + UpdateEncoderV1, + UpdateEncoderV2, + writeIdSet, + YXmlElement, + YXmlHook } from '../internals.js' /** @@ -108,7 +128,7 @@ export const logUpdateV2 = (update, YDecoder = UpdateDecoderV2) => { structs.push(curr) } logging.print('Structs: ', structs) - const ds = readDeleteSet(updateDecoder) + const ds = readIdSet(updateDecoder) logging.print('DeleteSet: ', ds) } @@ -132,7 +152,7 @@ export const decodeUpdateV2 = (update, YDecoder = UpdateDecoderV2) => { } return { structs, - ds: readDeleteSet(updateDecoder) + ds: readIdSet(updateDecoder) } } @@ -167,11 +187,11 @@ export const mergeUpdates = updates => mergeUpdatesV2(updates, UpdateDecoderV1, /** * @param {Uint8Array} update - * @param {typeof DSEncoderV1 | typeof DSEncoderV2} YEncoder + * @param {typeof IdSetEncoderV1 | typeof IdSetEncoderV2} YEncoder * @param {typeof UpdateDecoderV1 | typeof UpdateDecoderV2} YDecoder * @return {Uint8Array} */ -export const encodeStateVectorFromUpdateV2 = (update, YEncoder = DSEncoderV2, YDecoder = UpdateDecoderV2) => { +export const encodeStateVectorFromUpdateV2 = (update, YEncoder = IdSetEncoderV2, YDecoder = UpdateDecoderV2) => { const encoder = new YEncoder() const updateDecoder = new LazyStructReader(new YDecoder(decoding.createDecoder(update)), false) let curr = updateDecoder.curr @@ -223,7 +243,7 @@ export const encodeStateVectorFromUpdateV2 = (update, YEncoder = DSEncoderV2, YD * @param {Uint8Array} update * @return {Uint8Array} */ -export const encodeStateVectorFromUpdate = update => encodeStateVectorFromUpdateV2(update, DSEncoderV1, UpdateDecoderV1) +export const encodeStateVectorFromUpdate = update => encodeStateVectorFromUpdateV2(update, IdSetEncoderV1, UpdateDecoderV1) /** * @param {Uint8Array} update @@ -432,9 +452,9 @@ export const mergeUpdatesV2 = (updates, YDecoder = UpdateDecoderV2, YEncoder = U } finishLazyStructWriting(lazyStructEncoder) - const dss = updateDecoders.map(decoder => readDeleteSet(decoder)) - const ds = mergeDeleteSets(dss) - writeDeleteSet(updateEncoder, ds) + const dss = updateDecoders.map(decoder => readIdSet(decoder)) + const ds = mergeIdSets(dss) + writeIdSet(updateEncoder, ds) return updateEncoder.toUint8Array() } @@ -475,8 +495,8 @@ export const diffUpdateV2 = (update, sv, YDecoder = UpdateDecoderV2, YEncoder = } finishLazyStructWriting(lazyStructWriter) // write ds - const ds = readDeleteSet(decoder) - writeDeleteSet(encoder, ds) + const ds = readIdSet(decoder) + writeIdSet(encoder, ds) return encoder.toUint8Array() } @@ -514,7 +534,7 @@ const writeStructToLazyStructWriter = (lazyWriter, struct, offset) => { // write startClock encoding.writeVarUint(lazyWriter.encoder.restEncoder, struct.id.clock + offset) } - struct.write(lazyWriter.encoder, offset) + struct.write(lazyWriter.encoder, offset, 0) lazyWriter.written++ } /** @@ -552,30 +572,151 @@ const finishLazyStructWriting = (lazyWriter) => { /** * @param {Uint8Array} update + * @param {function(Item|GC|Skip):Item|GC|Skip} blockTransformer * @param {typeof UpdateDecoderV2 | typeof UpdateDecoderV1} YDecoder * @param {typeof UpdateEncoderV2 | typeof UpdateEncoderV1 } YEncoder */ -export const convertUpdateFormat = (update, YDecoder, YEncoder) => { +export const convertUpdateFormat = (update, blockTransformer, YDecoder, YEncoder) => { const updateDecoder = new YDecoder(decoding.createDecoder(update)) const lazyDecoder = new LazyStructReader(updateDecoder, false) const updateEncoder = new YEncoder() const lazyWriter = new LazyStructWriter(updateEncoder) - for (let curr = lazyDecoder.curr; curr !== null; curr = lazyDecoder.next()) { - writeStructToLazyStructWriter(lazyWriter, curr, 0) + writeStructToLazyStructWriter(lazyWriter, blockTransformer(curr), 0) } finishLazyStructWriting(lazyWriter) - const ds = readDeleteSet(updateDecoder) - writeDeleteSet(updateEncoder, ds) + const ds = readIdSet(updateDecoder) + writeIdSet(updateEncoder, ds) return updateEncoder.toUint8Array() } +/** + * @typedef {Object} ObfuscatorOptions + * @property {boolean} [ObfuscatorOptions.formatting=true] + * @property {boolean} [ObfuscatorOptions.subdocs=true] + * @property {boolean} [ObfuscatorOptions.yxml=true] Whether to obfuscate nodeName / hookName + */ + +/** + * @param {ObfuscatorOptions} obfuscator + */ +const createObfuscator = ({ formatting = true, subdocs = true, yxml = true } = {}) => { + let i = 0 + const mapKeyCache = map.create() + const nodeNameCache = map.create() + const formattingKeyCache = map.create() + const formattingValueCache = map.create() + formattingValueCache.set(null, null) // end of a formatting range should always be the end of a formatting range + /** + * @param {Item|GC|Skip} block + * @return {Item|GC|Skip} + */ + return block => { + switch (block.constructor) { + case GC: + case Skip: + return block + case Item: { + const item = /** @type {Item} */ (block) + const content = item.content + switch (content.constructor) { + case ContentDeleted: + break + case ContentType: { + if (yxml) { + const type = /** @type {ContentType} */ (content).type + if (type instanceof YXmlElement) { + type.nodeName = map.setIfUndefined(nodeNameCache, type.nodeName, () => 'node-' + i) + } + if (type instanceof YXmlHook) { + type.hookName = map.setIfUndefined(nodeNameCache, type.hookName, () => 'hook-' + i) + } + } + break + } + case ContentAny: { + const c = /** @type {ContentAny} */ (content) + c.arr = c.arr.map(() => i) + break + } + case ContentBinary: { + const c = /** @type {ContentBinary} */ (content) + c.content = new Uint8Array([i]) + break + } + case ContentDoc: { + const c = /** @type {ContentDoc} */ (content) + if (subdocs) { + c.opts = {} + c.doc.guid = i + '' + } + break + } + case ContentEmbed: { + const c = /** @type {ContentEmbed} */ (content) + c.embed = {} + break + } + case ContentFormat: { + const c = /** @type {ContentFormat} */ (content) + if (formatting) { + c.key = map.setIfUndefined(formattingKeyCache, c.key, () => i + '') + c.value = map.setIfUndefined(formattingValueCache, c.value, () => ({ i })) + } + break + } + case ContentJSON: { + const c = /** @type {ContentJSON} */ (content) + c.arr = c.arr.map(() => i) + break + } + case ContentString: { + const c = /** @type {ContentString} */ (content) + c.str = string.repeat((i % 10) + '', c.str.length) + break + } + default: + // unknown content type + error.unexpectedCase() + } + if (item.parentSub) { + item.parentSub = map.setIfUndefined(mapKeyCache, item.parentSub, () => i + '') + } + i++ + return block + } + default: + // unknown block-type + error.unexpectedCase() + } + } +} + +/** + * This function obfuscates the content of a Yjs update. This is useful to share + * buggy Yjs documents while significantly limiting the possibility that a + * developer can on the user. Note that it might still be possible to deduce + * some information by analyzing the "structure" of the document or by analyzing + * the typing behavior using the CRDT-related metadata that is still kept fully + * intact. + * + * @param {Uint8Array} update + * @param {ObfuscatorOptions} [opts] + */ +export const obfuscateUpdate = (update, opts) => convertUpdateFormat(update, createObfuscator(opts), UpdateDecoderV1, UpdateEncoderV1) + +/** + * @param {Uint8Array} update + * @param {ObfuscatorOptions} [opts] + */ +export const obfuscateUpdateV2 = (update, opts) => convertUpdateFormat(update, createObfuscator(opts), UpdateDecoderV2, UpdateEncoderV2) + /** * @param {Uint8Array} update */ -export const convertUpdateFormatV1ToV2 = update => convertUpdateFormat(update, UpdateDecoderV1, UpdateEncoderV2) +export const convertUpdateFormatV1ToV2 = update => convertUpdateFormat(update, f.id, UpdateDecoderV1, UpdateEncoderV2) /** * @param {Uint8Array} update */ -export const convertUpdateFormatV2ToV1 = update => convertUpdateFormat(update, UpdateDecoderV2, UpdateEncoderV1) +export const convertUpdateFormatV2ToV1 = update => convertUpdateFormat(update, f.id, UpdateDecoderV2, UpdateEncoderV1) diff --git a/test.html b/test.html index c3b3ab2de..c1dc0dfa8 100644 --- a/test.html +++ b/test.html @@ -1,9 +1,339 @@ + - Testing Yjs + Testing yjs + - + + diff --git a/tests/IdMap.tests.js b/tests/IdMap.tests.js new file mode 100644 index 000000000..38db22e88 --- /dev/null +++ b/tests/IdMap.tests.js @@ -0,0 +1,196 @@ +import * as t from 'lib0/testing' +import * as idmap from '../src/utils/IdMap.js' +import * as prng from 'lib0/prng' +import * as math from 'lib0/math' +import { compareIdmaps as compareIdMaps, createIdMap, ID, createRandomIdSet, createRandomIdMap, createAttributionItem } from './testHelper.js' +import * as YY from '../src/internals.js' + +/** + * @template T + * @param {Array<[number, number, number, Array]>} ops + */ +const simpleConstructAttrs = ops => { + const attrs = createIdMap() + ops.forEach(op => { + attrs.add(op[0], op[1], op[2], op[3].map(v => createAttributionItem('', v))) + }) + return attrs +} + +/** + * @param {t.TestCase} _tc + */ +export const testAmMerge = _tc => { + const attrs = [42] + t.group('filter out empty items (1))', () => { + compareIdMaps( + simpleConstructAttrs([[0, 1, 0, attrs]]), + simpleConstructAttrs([]) + ) + }) + t.group('filter out empty items (2))', () => { + compareIdMaps( + simpleConstructAttrs([[0, 1, 0, attrs], [0, 2, 0, attrs]]), + simpleConstructAttrs([]) + ) + }) + t.group('filter out empty items (3 - end))', () => { + compareIdMaps( + simpleConstructAttrs([[0, 1, 1, attrs], [0, 2, 0, attrs]]), + simpleConstructAttrs([[0, 1, 1, attrs]]) + ) + }) + t.group('filter out empty items (4 - middle))', () => { + compareIdMaps( + simpleConstructAttrs([[0, 1, 1, attrs], [0, 2, 0, attrs], [0, 3, 1, attrs]]), + simpleConstructAttrs([[0, 1, 1, attrs], [0, 3, 1, attrs]]) + ) + }) + t.group('filter out empty items (5 - beginning))', () => { + compareIdMaps( + simpleConstructAttrs([[0, 1, 0, attrs], [0, 2, 1, attrs], [0, 3, 1, attrs]]), + simpleConstructAttrs([[0, 2, 1, attrs], [0, 3, 1, attrs]]) + ) + }) + t.group('merge of overlapping id ranges', () => { + compareIdMaps( + simpleConstructAttrs([[0, 1, 2, attrs], [0, 0, 2, attrs]]), + simpleConstructAttrs([[0, 0, 3, attrs]]) + ) + }) + t.group('construct without hole', () => { + compareIdMaps( + simpleConstructAttrs([[0, 1, 2, attrs], [0, 3, 1, attrs]]), + simpleConstructAttrs([[0, 1, 3, attrs]]) + ) + }) + t.group('no merge of overlapping id ranges with different attributes', () => { + compareIdMaps( + simpleConstructAttrs([[0, 1, 2, [1]], [0, 0, 2, [2]]]), + simpleConstructAttrs([[0, 0, 1, [2]], [0, 1, 1, [1, 2]], [0, 2, 1, [1]]]) + ) + }) +} + +/** + * @param {t.TestCase} tc + */ +export const testRepeatMergingMultipleIdMaps = tc => { + const clients = 4 + const clockRange = 5 + /** + * @type {Array>} + */ + const sets = [] + for (let i = 0; i < 3; i++) { + sets.push(createRandomIdMap(tc.prng, clients, clockRange, [1, 2, 3])) + } + const merged = idmap.mergeIdMaps(sets) + const mergedReverse = idmap.mergeIdMaps(sets.reverse()) + compareIdMaps(merged, mergedReverse) + const composed = idmap.createIdMap() + for (let iclient = 0; iclient < clients; iclient++) { + for (let iclock = 0; iclock < clockRange + 42; iclock++) { + const mergedHas = merged.hasId(new ID(iclient, iclock)) + const oneHas = sets.some(ids => ids.hasId(new ID(iclient, iclock))) + t.assert(mergedHas === oneHas) + const mergedAttrs = merged.sliceId(new ID(iclient, iclock), 1) + mergedAttrs.forEach(a => { + if (a.attrs != null) { + composed.add(iclient, a.clock, a.len, a.attrs) + } + }) + } + } + compareIdMaps(merged, composed) +} + +/** + * @param {t.TestCase} tc + */ +export const testRepeatRandomDiffing = tc => { + const clients = 4 + const clockRange = 100 + const attrs = [1, 2, 3] + const idset1 = createRandomIdMap(tc.prng, clients, clockRange, attrs) + const idset2 = createRandomIdMap(tc.prng, clients, clockRange, attrs) + const merged = idmap.mergeIdMaps([idset1, idset2]) + const e1 = idmap.diffIdMap(idset1, idset2) + const e2 = idmap.diffIdMap(merged, idset2) + compareIdMaps(e1, e2) + const copy = YY.decodeIdMap(YY.encodeIdMap(e1)) + compareIdMaps(e1, copy) +} + +/** + * @param {t.TestCase} tc + */ +export const testRepeatRandomDiffing2 = tc => { + const clients = 4 + const clockRange = 100 + const attrs = [1, 2, 3] + const idmap1 = createRandomIdMap(tc.prng, clients, clockRange, attrs) + const idmap2 = createRandomIdMap(tc.prng, clients, clockRange, attrs) + const idsExclude = createRandomIdSet(tc.prng, clients, clockRange) + const merged = idmap.mergeIdMaps([idmap1, idmap2]) + const mergedExcluded = idmap.diffIdMap(merged, idsExclude) + const e1 = idmap.diffIdMap(idmap1, idsExclude) + const e2 = idmap.diffIdMap(idmap2, idsExclude) + const excludedMerged = idmap.mergeIdMaps([e1, e2]) + compareIdMaps(mergedExcluded, excludedMerged) + const copy = YY.decodeIdMap(YY.encodeIdMap(mergedExcluded)) + compareIdMaps(mergedExcluded, copy) +} + +/** + * @param {t.TestCase} tc + */ +export const testRepeatRandomDeletes = tc => { + const clients = 1 + const clockRange = 100 + const idset = createRandomIdMap(tc.prng, clients, clockRange, []) + const client = Array.from(idset.clients.keys())[0] + const clock = prng.int31(tc.prng, 0, clockRange) + const len = prng.int31(tc.prng, 0, math.round((clockRange - clock) * 1.2)) // allow exceeding range to cover more edge cases + const idsetOfDeletes = idmap.createIdMap() + idsetOfDeletes.add(client, clock, len, []) + const diffed = idmap.diffIdMap(idset, idsetOfDeletes) + idset.delete(client, clock, len) + for (let i = 0; i < len; i++) { + t.assert(!idset.has(client, clock + i)) + } + compareIdMaps(idset, diffed) +} + +/** + * @param {t.TestCase} tc + */ +export const testrepeatRandomIntersects = tc => { + const clients = 4 + const clockRange = 100 + const ids1 = createRandomIdMap(tc.prng, clients, clockRange, [1]) + const ids2 = createRandomIdMap(tc.prng, clients, clockRange, ['two']) + const intersected = idmap.intersectMaps(ids1, ids2) + for (let client = 0; client < clients; client++) { + for (let clock = 0; clock < clockRange; clock++) { + t.assert((ids1.has(client, clock) && ids2.has(client, clock)) === intersected.has(client, clock)) + /** + * @type {Array?} + */ + const slice1 = ids1.slice(client, clock, 1)[0].attrs + /** + * @type {Array?} + */ + const slice2 = ids2.slice(client, clock, 1)[0].attrs + /** + * @type {Array?} + */ + const expectedAttrs = (slice1 != null && slice2 != null) ? slice1.concat(slice2) : null + const attrs = intersected.slice(client, clock, 1)[0].attrs + t.assert(attrs?.length === expectedAttrs?.length) + } + } + const diffed1 = idmap.diffIdMap(ids1, ids2) + const altDiffed1 = idmap.diffIdMap(ids1, intersected) + compareIdMaps(diffed1, altDiffed1) +} diff --git a/tests/IdSet.tests.js b/tests/IdSet.tests.js new file mode 100644 index 000000000..33ad50c51 --- /dev/null +++ b/tests/IdSet.tests.js @@ -0,0 +1,242 @@ +import * as t from 'lib0/testing' +import * as d from '../src/utils/IdSet.js' +import * as math from 'lib0/math' +import * as prng from 'lib0/prng' +import { compareIdSets, createRandomIdSet, ID } from './testHelper.js' + +/** + * @param {Array<[number, number, number]>} ops + */ +const simpleConstructIdSet = ops => { + const idset = d.createIdSet() + ops.forEach(op => { + d.addToIdSet(idset, op[0], op[1], op[2]) + }) + return idset +} + +/** + * @param {t.TestCase} _tc + */ +export const testIdsetMerge = _tc => { + t.group('filter out empty items (1))', () => { + compareIdSets( + simpleConstructIdSet([[0, 1, 0]]), + simpleConstructIdSet([]) + ) + }) + t.group('filter out empty items (2))', () => { + compareIdSets( + simpleConstructIdSet([[0, 1, 0], [0, 2, 0]]), + simpleConstructIdSet([]) + ) + }) + t.group('filter out empty items (3 - end))', () => { + compareIdSets( + simpleConstructIdSet([[0, 1, 1], [0, 2, 0]]), + simpleConstructIdSet([[0, 1, 1]]) + ) + }) + t.group('filter out empty items (4 - middle))', () => { + compareIdSets( + simpleConstructIdSet([[0, 1, 1], [0, 2, 0], [0, 3, 1]]), + simpleConstructIdSet([[0, 1, 1], [0, 3, 1]]) + ) + }) + t.group('filter out empty items (5 - beginning))', () => { + compareIdSets( + simpleConstructIdSet([[0, 1, 0], [0, 2, 1], [0, 3, 1]]), + simpleConstructIdSet([[0, 2, 1], [0, 3, 1]]) + ) + }) + t.group('merge of overlapping id ranges', () => { + compareIdSets( + simpleConstructIdSet([[0, 1, 2], [0, 0, 2]]), + simpleConstructIdSet([[0, 0, 3]]) + ) + }) + t.group('construct without hole', () => { + compareIdSets( + simpleConstructIdSet([[0, 1, 2], [0, 3, 1]]), + simpleConstructIdSet([[0, 1, 3]]) + ) + }) +} + +/** + * @param {t.TestCase} _tc + */ +export const testDiffing = _tc => { + t.group('simple case (1))', () => { + compareIdSets( + d.diffIdSet( + simpleConstructIdSet([[0, 1, 1], [0, 3, 1]]), + simpleConstructIdSet([[0, 3, 1]]) + ), + simpleConstructIdSet([[0, 1, 1]]) + ) + }) + t.group('subset left', () => { + compareIdSets( + d.diffIdSet( + simpleConstructIdSet([[0, 1, 3]]), + simpleConstructIdSet([[0, 1, 1]]) + ), + simpleConstructIdSet([[0, 2, 2]]) + ) + }) + t.group('subset right', () => { + compareIdSets( + d.diffIdSet( + simpleConstructIdSet([[0, 1, 3]]), + simpleConstructIdSet([[0, 3, 1]]) + ), + simpleConstructIdSet([[0, 1, 2]]) + ) + }) + t.group('subset middle', () => { + compareIdSets( + d.diffIdSet( + simpleConstructIdSet([[0, 1, 3]]), + simpleConstructIdSet([[0, 2, 1]]) + ), + simpleConstructIdSet([[0, 1, 1], [0, 3, 1]]) + ) + }) + t.group('overlapping left', () => { + compareIdSets( + d.diffIdSet( + simpleConstructIdSet([[0, 1, 3]]), + simpleConstructIdSet([[0, 0, 2]]) + ), + simpleConstructIdSet([[0, 2, 2]]) + ) + }) + t.group('overlapping right', () => { + compareIdSets( + d.diffIdSet( + simpleConstructIdSet([[0, 1, 3]]), + simpleConstructIdSet([[0, 3, 5]]) + ), + simpleConstructIdSet([[0, 1, 2]]) + ) + }) + t.group('overlapping completely', () => { + compareIdSets( + d.diffIdSet( + simpleConstructIdSet([[0, 1, 3]]), + simpleConstructIdSet([[0, 0, 5]]) + ), + simpleConstructIdSet([]) + ) + }) + t.group('overlapping into new range', () => { + compareIdSets( + d.diffIdSet( + simpleConstructIdSet([[0, 1, 3], [0, 5, 2]]), + simpleConstructIdSet([[0, 0, 6]]) + ), + simpleConstructIdSet([[0, 6, 1]]) + ) + }) +} + +/** + * @param {t.TestCase} tc + */ +export const testRepeatRandomDiffing = tc => { + const clients = 4 + const clockRange = 100 + const ds1 = createRandomIdSet(tc.prng, clients, clockRange) + const ds2 = createRandomIdSet(tc.prng, clients, clockRange) + const merged = d.mergeIdSets([ds1, ds2]) + const e1 = d.diffIdSet(ds1, ds2) + const e2 = d.diffIdSet(merged, ds2) + compareIdSets(e1, e2) +} + +/** + * @param {t.TestCase} tc + */ +export const testRepeatRandomDeletes = tc => { + const clients = 1 + const clockRange = 100 + const idset = createRandomIdSet(tc.prng, clients, clockRange) + const client = Array.from(idset.clients.keys())[0] + const clock = prng.int31(tc.prng, 0, clockRange) + const len = prng.int31(tc.prng, 0, math.round((clockRange - clock) * 1.2)) // allow exceeding range to cover more edge cases + const idsetOfDeletes = d.createIdSet() + idsetOfDeletes.add(client, clock, len) + const diffed = d.diffIdSet(idset, idsetOfDeletes) + idset.delete(client, clock, len) + for (let i = 0; i < len; i++) { + t.assert(!idset.has(client, clock + i)) + } + compareIdSets(idset, diffed) +} + +/** + * @param {t.TestCase} tc + */ +export const testRepeatMergingMultipleIdsets = tc => { + const clients = 4 + const clockRange = 100 + /** + * @type {Array} + */ + const idss = [] + for (let i = 0; i < 3; i++) { + idss.push(createRandomIdSet(tc.prng, clients, clockRange)) + } + const merged = d.mergeIdSets(idss) + const mergedReverse = d.mergeIdSets(idss.reverse()) + compareIdSets(merged, mergedReverse) + const composed = d.createIdSet() + for (let iclient = 0; iclient < clients; iclient++) { + for (let iclock = 0; iclock < clockRange + 42; iclock++) { + const mergedHas = merged.hasId(new ID(iclient, iclock)) + const oneHas = idss.some(ids => ids.hasId(new ID(iclient, iclock))) + t.assert(mergedHas === oneHas) + if (oneHas) { + d.addToIdSet(composed, iclient, iclock, 1) + } + } + } + compareIdSets(merged, composed) +} + +/** + * @param {t.TestCase} tc + */ +export const testRepeatRandomDiffing2 = tc => { + const clients = 4 + const clockRange = 100 + const ids1 = createRandomIdSet(tc.prng, clients, clockRange) + const ids2 = createRandomIdSet(tc.prng, clients, clockRange) + const idsExclude = createRandomIdSet(tc.prng, clients, clockRange) + const merged = d.mergeIdSets([ids1, ids2]) + const mergedExcluded = d.diffIdSet(merged, idsExclude) + const e1 = d.diffIdSet(ids1, idsExclude) + const e2 = d.diffIdSet(ids2, idsExclude) + const excludedMerged = d.mergeIdSets([e1, e2]) + compareIdSets(mergedExcluded, excludedMerged) +} + +/** + * @param {t.TestCase} tc + */ +export const testrepeatRandomIntersects = tc => { + const clients = 4 + const clockRange = 100 + const ids1 = createRandomIdSet(tc.prng, clients, clockRange) + const ids2 = createRandomIdSet(tc.prng, clients, clockRange) + const intersected = d.intersectSets(ids1, ids2) + for (let client = 0; client < clients; client++) { + for (let clock = 0; clock < clockRange; clock++) { + t.assert((ids1.has(client, clock) && ids2.has(client, clock)) === intersected.has(client, clock)) + } + } + const diffed1 = d.diffIdSet(ids1, ids2) + const altDiffed1 = d.diffIdSet(ids1, intersected) + compareIdSets(diffed1, altDiffed1) +} diff --git a/tests/attribution.tests.js b/tests/attribution.tests.js new file mode 100644 index 000000000..d44d26cea --- /dev/null +++ b/tests/attribution.tests.js @@ -0,0 +1,88 @@ +/** + * Testing if encoding/decoding compatibility and integration compatibility is given. + * We expect that the document always looks the same, even if we upgrade the integration algorithm, or add additional encoding approaches. + * + * The v1 documents were generated with Yjs v13.2.0 based on the randomisized tests. + */ + +import * as Y from '../src/index.js' +import * as t from 'lib0/testing' +import * as delta from 'lib0/delta' + +/** + * @param {t.TestCase} _tc + */ +export const testRelativePositions = _tc => { + const ydoc = new Y.Doc() + const ytext = ydoc.getText() + ytext.insert(0, 'hello world') + const v1 = Y.cloneDoc(ydoc) + ytext.delete(1, 6) + ytext.insert(1, 'x') + const am = Y.createAttributionManagerFromDiff(v1, ydoc) + const rel = Y.createRelativePositionFromTypeIndex(ytext, 9, 1, am) // pos after "hello wo" + const abs1 = Y.createAbsolutePositionFromRelativePosition(rel, ydoc, true, am) + const abs2 = Y.createAbsolutePositionFromRelativePosition(rel, ydoc, true) + t.assert(abs1?.index === 9) + t.assert(abs2?.index === 3) +} + +/** + * @param {t.TestCase} _tc + */ +export const testAttributedEvents = _tc => { + const ydoc = new Y.Doc() + const ytext = ydoc.getText() + ytext.insert(0, 'hello world') + const v1 = Y.cloneDoc(ydoc) + ydoc.transact(() => { + ytext.delete(6, 5) + }) + const am = Y.createAttributionManagerFromDiff(v1, ydoc) + const c1 = ytext.getContent(am) + t.compare(c1, delta.text().insert('hello ').insert('world', null, { delete: [] })) + let calledObserver = false + ytext.observe(event => { + const d = event.getDelta(am) + t.compare(d, delta.text().retain(11).insert('!', null, { insert: [] })) + calledObserver = true + }) + ytext.insert(11, '!') + t.assert(calledObserver) +} + +/** + * @param {t.TestCase} _tc + */ +export const testInsertionsMindingAttributedContent = _tc => { + const ydoc = new Y.Doc() + const ytext = ydoc.getText() + ytext.insert(0, 'hello world') + const v1 = Y.cloneDoc(ydoc) + ydoc.transact(() => { + ytext.delete(6, 5) + }) + const am = Y.createAttributionManagerFromDiff(v1, ydoc) + const c1 = ytext.getContent(am) + t.compare(c1, delta.text().insert('hello ').insert('world', null, { delete: [] })) + ytext.applyDelta(delta.text().retain(11).insert('content'), am) + t.assert(ytext.toString() === 'hello content') +} + +/** + * @param {t.TestCase} _tc + */ +export const testInsertionsIntoAttributedContent = _tc => { + const ydoc = new Y.Doc() + const ytext = ydoc.getText() + ytext.insert(0, 'hello ') + const v1 = Y.cloneDoc(ydoc) + ydoc.transact(() => { + ytext.insert(6, 'word') + }) + const am = Y.createAttributionManagerFromDiff(v1, ydoc) + const c1 = ytext.getContent(am) + t.compare(c1, delta.text().insert('hello ').insert('word', null, { insert: [] })) + ytext.applyDelta(delta.text().retain(9).insert('l'), am) + t.assert(ytext.toString() === 'hello world') +} diff --git a/tests/compatibility.tests.js b/tests/compatibility.tests.js index 3a7ad4fe0..674d68628 100644 --- a/tests/compatibility.tests.js +++ b/tests/compatibility.tests.js @@ -1,6 +1,5 @@ - /** - * Testing if encoding/decoding compatibility and integration compatiblity is given. + * Testing if encoding/decoding compatibility and integration compatibility is given. * We expect that the document always looks the same, even if we upgrade the integration algorithm, or add additional encoding approaches. * * The v1 documents were generated with Yjs v13.2.0 based on the randomisized tests. @@ -11,9 +10,9 @@ import * as t from 'lib0/testing' import * as buffer from 'lib0/buffer' /** - * @param {t.TestCase} tc + * @param {t.TestCase} _tc */ -export const testArrayCompatibilityV1 = tc => { +export const testArrayCompatibilityV1 = _tc => { const oldDoc = 'BV8EAAcBBWFycmF5AAgABAADfQF9An0DgQQDAYEEAAEABMEDAAQAAccEAAQFASEABAsIc29tZXByb3ACqAQNAX0syAQLBAUBfYoHwQQPBAUBwQQQBAUByAQRBAUBfYoHyAQQBBEBfY0HyAQTBBEBfY0HyAQUBBEBfY0HyAQVBBEBfY0HyAQQBBMBfY4HyAQXBBMBfY4HwQQYBBMBxwQXBBgACAAEGgR9AX0CfQN9BMEBAAEBAQADxwQLBA8BIQAEIwhzb21lcHJvcAKoBCUBfSzHBBkEEwEhAAQnCHNvbWVwcm9wAqgEKQF9LMcCAAMAASEABCsIc29tZXByb3ACqAQtAX0syAEBAQIBfZMHyAQvAQIBfZMHwQEGAQcBAAPBBDEBBwEABMcBGQEVAAgABDoEfQF9An0DfQTHAAgADgAIAAQ/BH0BfQJ9A30ExwQYBBkACAAERAR9AX0CfQN9BMcEIwQPASEABEkIc29tZXByb3ACqARLAX0swQAKAAkBxwEZBDoACAAETgR9AX0CfQN9BMcEEAQXAAgABFMEfQF9An0DfQTHAxsDHAAIAARYBH0BfQJ9A30ExwECAQ0BIQAEXQhzb21lcHJvcAKoBF8BfSzHAQQBBQAIAARhBH0BfQJ9A30ExwABAAYBIQAEZghzb21lcHJvcAKoBGgBfSzHAywDLQEhAARqCHNvbWVwcm9wAqgEbAF9LMcCCgMPASEABG4Ic29tZXByb3ACqARwAX0sxwMfAQABIQAEcghzb21lcHJvcAKoBHQBfSzHABcAGAEhAAR2CHNvbWVwcm9wAqgEeAF9LMcCEwMfAAgABHoEfQF9An0DfQTHARYBFwAIAAR/BH0BfQJ9A30ExwAIBD8BIQAEhAEIc29tZXByb3ACqASGAQF9LMcAGQAPAAgABIgBBH0BfQJ9A30ExwMBAScACAAEjQEEfQF9An0DfQTHAB4CDgEhAASSAQhzb21lcHJvcAKoBJQBAX0syAErAR4EfYQIfYQIfYQIfYQIxwB7AHwBIQAEmgEIc29tZXByb3ACqAScAQF9LMgBRgIrA32ICH2ICH2ICMgAEgAIAn2KCH2KCHADAAEBBWFycmF5AYcDAAEhAAMBCHNvbWVwcm9wAqgDAwF9LIEDAQEABIEDBQEABEECAAHIAw8CAAF9hwfIAxACAAF9hwfBAxECAAHHAAEAAgEhAAMTCHNvbWVwcm9wAqgDFQF9LIEEAAKIAxgBfYwHyAMPAxABfY8HwQMaAxAByAMbAxABfY8HyAMPAxoBfZAHyAMdAxoBfZAHxwACAw8BIQADHwhzb21lcHJvcAKoAyEBfSzHAxoDGwEhAAMjCHNvbWVwcm9wAqgDJQF9LMcCAAMAASEAAycIc29tZXByb3ACqAMpAX0swQMQAxEByAMrAxEBfZIHyAMsAxEBfZIHyAMtAxEBfZIHwQMYAxkBAATIAQYBBwF9lAfIAzQBBwF9lAfHAQcELwAIAAM2BH0BfQJ9A30EyAEBAR4CfZUHfZUHyAMsAy0DfZcHfZcHfZcHxwQTBBQBIQADQAhzb21lcHJvcAKoA0IBfSxIAAACfZgHfZgHyANFAAABfZgHxwEEAQUACAADRwR9AX0CfQN9BMgDQAQUAX2ZB8EDTAQUAscABgIXASEAA08Ic29tZXByb3ACqANRAX0syAM/Ay0BfZwHyAMfAQABfZ0HxwM2BC8ACAADVQR9AX0CfQN9BMcDRQNGASEAA1oIc29tZXByb3ACqANcAX0sxwMPAx0BIQADXghzb21lcHJvcAKoA2ABfSzIAQgBBgF9pAfIAQQDRwN9pwd9pwd9pwfIAA8AEAJ9rAd9rAfHAAAAAwAIAANoBH0BfQJ9A30EyAMQAysDfbIHfbIHfbIHxwQxAQcACAADcAR9AX0CfQN9BMcBAAQfASEAA3UIc29tZXByb3ACqAN3AX0syAM/A1MBfbUHyAN5A1MCfbUHfbUHyAMtAy4DfbcHfbcHfbcHyAACAhMCfbkHfbkHyAOAAQITAX25B8cBKwM7AAgAA4IBBH0BfQJ9A30ExwEZARUBIQADhwEIc29tZXByb3ACqAOJAQF9LMcCHAQLAAgAA4sBBH0BfQJ9A30EyAQZBCcBfbsHyAOQAQQnAn27B327B8cDkAEDkQEBIQADkwEIc29tZXByb3ACqAOVAQF9LMcDaAADAAgAA5cBBH0BfQJ9A30ExwN5A3oACAADnAEEfQF9An0DfQTHA4sBBAsACAADoQEEfQF9An0DfQTHA5MBA5EBASEAA6YBCHNvbWVwcm9wAqgDqAEBfSzHAAADaAAIAAOqAQR9AX0CfQN9BMgADgAZA328B328B328B8gECwQjBH2CCH2CCH2CCH2CCMcDLQN8ASEAA7YBCHNvbWVwcm9wAqgDuAEBfSzHBAoEAAAIAAO6AQR9AX0CfQN9BMgDgAEDgQECfYUIfYUIWgIAAQEFYXJyYXkBAARHAgAACAACBQR9AX0CfQN9BMECBQIAAQADwQIFAgoBAATBAAICBQEAA8cABgAHAAgAAhcEfQF9An0DfQTHAxkECwEhAAIcCHNvbWVwcm9wAqgCHgF9LMcABAAFASEAAiAIc29tZXByb3ACqAIiAX0syAAIAA4BfZYHyAMRAxIBfZoHxwMdAx4ACAACJgR9AX0CfQN9BMcEFgQRAAgAAisEfQF9An0DfQTHBAoEAAAIAAIwBH0BfQJ9A30EyAAOABkDfaAHfaAHfaAHxwEFACIACAACOAR9AX0CfQN9BMcDJwQrAAgAAj0EfQF9An0DfQTHAhcABwAIAAJCBH0BfQJ9A30EyAEABB8CfaUHfaUHxwQrAwABIQACSQhzb21lcHJvcAKoAksBfSzHBCcEEwAIAAJNBH0BfQJ9A30ExwMbAxwACAACUgR9AX0CfQN9BMcEJwJNASEAAlcIc29tZXByb3ACqAJZAX0sxwQvBDAACAACWwR9AX0CfQN9BMcCPQQrASEAAmAIc29tZXByb3ACqAJiAX0sxwAYAycBIQACZAhzb21lcHJvcAKoAmYBfSzIAQEBHgJ9swd9swfIAmQDJwN9tAd9tAd9tAfHAkkDAAAIAAJtBH0BfQJ9A30ExwJkAmoACAACcgR9AX0CfQN9BMcCJgMeAAgAAncEfQF9An0DfQTHAiUDEgEhAAJ8CHNvbWVwcm9wAqgCfgF9LMgBFwEYBH24B324B324B324B8cBAQJoASEAAoQBCHNvbWVwcm9wAqgChgEBfSzHAkkCbQAIAAKIAQR9AX0CfQN9BMcCSAQfASEAAo0BCHNvbWVwcm9wAqgCjwEBfSzIAQYEMQR9vgd9vgd9vgd9vgfHAAAAAwEhAAKVAQhzb21lcHJvcAKoApcBAX0sxwJNBBMBIQACmQEIc29tZXByb3ACqAKbAQF9LMcCJgJ3ASEAAp0BCHNvbWVwcm9wAqgCnwEBfSzHAAEABgAIAAKhAQR9AX0CfQN9BMgCjQEEHwF9gwjIAyMDGwF9hgjHBF0BDQAIAAKoAQR9AX0CfQN9BMcDPAEeAAgAAq0BBH0BfQJ9A30EagEAAQEFYXJyYXkByAEAAwABfYMHyAEBAwABfYMHwQECAwAByAEBAQIBfYYHyAEEAQIBfYYHyAEFAQIBfYYHwQEGAQIBxwEFAQYACAABCAR9AX0CfQN9BMEBAgEDAQAEwQEFAQgByAESAQgBfYsHyAETAQgBfYsHyAEUAQgBfYsHgQQAAYEBFgGIARcBfZEHxwEUARUACAABGQR9AX0CfQN9BMcBAQEEAAgAAR4EfQF9An0DfQTHARQBGQEhAAEjCHNvbWVwcm9wAqgBJQF9LMEDAQMFAQADxwEBAR4BIQABKwhzb21lcHJvcAKoAS0BfSzHAgUAHgEhAAEvCHNvbWVwcm9wAqgBMQF9LMcECwQjASEAATMIc29tZXByb3ACqAE1AX0sxwMtAy4ACAABNwR9AX0CfQN9BMcDDwMdAAgAATwEfQF9An0DfQTHAQIBDQAIAAFBBH0BfQJ9A30ExwQWBBEBIQABRghzb21lcHJvcAKoAUgBfSzBABgDJwHIAUoDJwF9nwfHBBcEGgAIAAFMBH0BfQJ9A30ExwEABB8BIQABUQhzb21lcHJvcAKoAVMBfSzIAx0DHgJ9oQd9oQfIARkBFQF9ogfIAhwECwN9qAd9qAd9qAfIAxEDEgF9qgfIBAABFgJ9qwd9qwfIABAAEQF9rQfIAV4AEQF9rQfIAV8AEQJ9rQd9rQfIAV4BXwR9rwd9rwd9rwd9rwfIABABXgN9sAd9sAd9sAfIAWgBXgF9sAfHBA8EEAAIAAFqBH0BfQJ9A30ExwQYBBkBIQABbwhzb21lcHJvcAKoAXEBfSzHAAcAEgEhAAFzCHNvbWVwcm9wAqgBdQF9LEcAAAAIAAF3BH0BfQJ9A30ExwMPATwBIQABfAhzb21lcHJvcAKoAX4BfSzIAXwBPAJ9ugd9ugfBAYEBATwCxwFoAWkACAABhAEEfQF9An0DfQTHAV8BYAAIAAGJAQR9AX0CfQN9BMcADgAZASEAAY4BCHNvbWVwcm9wAqgBkAEBfSzIAx8BAAF9vQfIAZIBAQABfb0HyAQVBBYCfb8Hfb8HxwQaBBgBIQABlgEIc29tZXByb3ACqAGYAQF9LMgBHgEEA32ACH2ACH2ACMcEGAFvAAgAAZ0BBH0BfQJ9A30ExwMTAAIBIQABogEIc29tZXByb3ACqAGkAQF9LMcBkgEBkwEBIQABpgEIc29tZXByb3ACqAGoAQF9LMcBnAEBBAEhAAGqAQhzb21lcHJvcAKoAawBAX0syAF8AYABBH2HCH2HCH2HCH2HCMgBpgEBkwEDfYkIfYkIfYkIYQAAAQEFYXJyYXkBiAAAAX2AB4EAAQHBAAAAAQLIAAQAAQF9gQfIAAEAAgF9hAfIAAYAAgF9hAfIAAcAAgF9hAfBAAgAAgHBAAgACQEAA8gACAAKAX2FB8EADgAKAcgADwAKAX2FB8gAEAAKAX2FB8cABwAIAAgAABIEfQF9An0DfQTIAgADAAF9iQfIABcDAAF9iQfHAA4ADwAIAAAZBH0BfQJ9A30ExwIFAgABIQAAHghzb21lcHJvcAKoACABfSzHAQUBEgEhAAAiCHNvbWVwcm9wAqgAJAF9LMcAHgIOAAgAACYEfQF9An0DfQTHBBQEFQAIAAArBH0BfQJ9A30ExwAAAAMACAAAMAR9AX0CfQN9BMcBBQAiAAgAADUEfQF9An0DfQTIAx4DGgN9mwd9mwd9mwfHAhcABwAIAAA9BH0BfQJ9A30ExwEYAxcBIQAAQghzb21lcHJvcAKoAEQBfSzBACIBEgEABMcDDwMdASEAAEsIc29tZXByb3ACqABNAX0sxwQYBBkBIQAATwhzb21lcHJvcAKoAFEBfSzHACIARgAIAABTBH0BfQJ9A30ExwMdAx4BIQAAWAhzb21lcHJvcAKoAFoBfSzIAB4AJgF9owfHAzYELwAIAABdBH0BfQJ9A30EyAQwAQIDfaYHfaYHfaYHyABkAQIBfakHyAAXABgCfa4Hfa4HxwQjBA8BIQAAaAhzb21lcHJvcAKoAGoBfSzHAycEKwAIAABsBH0BfQJ9A30ExwABAAYACAAAcQR9AX0CfQN9BMcAZABlAAgAAHYEfQF9An0DfQTIAAcAEgF9sQfIAHsAEgN9sQd9sQd9sQfIAA8AEAF9tgfHARMBFAAIAACAAQR9AX0CfQN9BMcDIwMbAAgAAIUBBH0BfQJ9A30ExwEVAQgACAAAigEEfQF9An0DfQTHAIoBAQgBIQAAjwEIc29tZXByb3ACqACRAQF9LMcCFwA9AAgAAJMBBH0BfQJ9A30ExwEYAEIACAAAmAEEfQF9An0DfQTHAzQDNQEhAACdAQhzb21lcHJvcAKoAJ8BAX0sxwAQABEBIQAAoQEIc29tZXByb3ACqACjAQF9LMgAgAEBFAF9gQjHBBYEEQEhAACmAQhzb21lcHJvcAKoAKgBAX0sxwAHAHsACAAAqgEEfQF9An0DfQQFABAAAQIDCQUPAR8CIwJDAkYFTAJQAlkCaQKQAQKeAQKiAQKnAQICDgAFCg0dAiECSgJYAmECZQJ9AoUBAo4BApYBApoBAp4BAgQUBAcMAhACGQEfBCQCKAIsAjEJSgJNAV4CZwJrAm8CcwJ3AoUBApMBApsBAgMWAAECAgULEgEUAhcCGwEgAiQCKAIrAS8FQQJNAlACWwJfAnYCiAEClAECpwECtwECARYAAQMBBwENBhYCJAInBCwCMAI0AkcCSgFSAnACdAJ9AoIBAo8BApcBAqMBAqcBAqsBAg==' const oldVal = JSON.parse('[[1,2,3,4],472,472,{"someprop":44},472,[1,2,3,4],{"someprop":44},[1,2,3,4],[1,2,3,4],[1,2,3,4],{"someprop":44},449,448,[1,2,3,4],[1,2,3,4],{"someprop":44},452,{"someprop":44},[1,2,3,4],[1,2,3,4],[1,2,3,4],[1,2,3,4],452,[1,2,3,4],497,{"someprop":44},497,497,497,{"someprop":44},[1,2,3,4],522,522,452,470,{"someprop":44},[1,2,3,4],453,{"someprop":44},480,480,480,508,508,508,[1,2,3,4],[1,2,3,4],502,492,492,453,{"someprop":44},496,496,496,[1,2,3,4],496,493,495,495,495,495,493,[1,2,3,4],493,493,453,{"someprop":44},{"someprop":44},505,505,517,517,505,[1,2,3,4],{"someprop":44},509,{"someprop":44},521,521,521,509,477,{"someprop":44},{"someprop":44},485,485,{"someprop":44},515,{"someprop":44},451,{"someprop":44},[1,2,3,4],516,516,516,516,{"someprop":44},499,499,469,469,[1,2,3,4],[1,2,3,4],512,512,512,{"someprop":44},454,487,487,487,[1,2,3,4],[1,2,3,4],454,[1,2,3,4],[1,2,3,4],{"someprop":44},[1,2,3,4],459,[1,2,3,4],513,459,{"someprop":44},[1,2,3,4],482,{"someprop":44},[1,2,3,4],[1,2,3,4],459,[1,2,3,4],{"someprop":44},[1,2,3,4],484,454,510,510,510,510,468,{"someprop":44},468,[1,2,3,4],[1,2,3,4],[1,2,3,4],[1,2,3,4],467,[1,2,3,4],467,486,486,486,[1,2,3,4],489,451,[1,2,3,4],{"someprop":44},[1,2,3,4],[1,2,3,4],{"someprop":44},{"someprop":44},483,[1,2,3,4],{"someprop":44},{"someprop":44},{"someprop":44},{"someprop":44},519,519,519,519,506,506,[1,2,3,4],{"someprop":44},464,{"someprop":44},481,481,[1,2,3,4],{"someprop":44},[1,2,3,4],464,475,475,475,463,{"someprop":44},[1,2,3,4],518,[1,2,3,4],[1,2,3,4],463,455,498,498,498,466,471,471,471,501,[1,2,3,4],501,501,476,{"someprop":44},466,[1,2,3,4],{"someprop":44},503,503,503,466,455,490,474,{"someprop":44},457,494,494,{"someprop":44},457,479,{"someprop":44},[1,2,3,4],500,500,500,{"someprop":44},[1,2,3,4],[1,2,3,4],{"someprop":44},{"someprop":44},{"someprop":44},[1,2,3,4],[1,2,3,4],{"someprop":44},[1,2,3,4],[1,2,3,4],[1,2,3,4],[1,2,3],491,491,[1,2,3,4],504,504,504,504,465,[1,2,3,4],{"someprop":44},460,{"someprop":44},488,488,488,[1,2,3,4],[1,2,3,4],{"someprop":44},{"someprop":44},514,514,514,514,{"someprop":44},{"someprop":44},{"someprop":44},458,[1,2,3,4],[1,2,3,4],462,[1,2,3,4],[1,2,3,4],{"someprop":44},462,{"someprop":44},[1,2,3,4],{"someprop":44},[1,2,3,4],507,{"someprop":44},{"someprop":44},507,507,{"someprop":44},{"someprop":44},[1,2,3,4],{"someprop":44},461,{"someprop":44},473,461,[1,2,3,4],461,511,511,461,{"someprop":44},{"someprop":44},520,520,520,[1,2,3,4],458]') const doc = new Y.Doc() @@ -22,9 +21,9 @@ export const testArrayCompatibilityV1 = tc => { } /** - * @param {t.TestCase} tc + * @param {t.TestCase} _tc */ -export const testMapDecodingCompatibilityV1 = tc => { +export const testMapDecodingCompatibilityV1 = _tc => { const oldDoc = 'BVcEAKEBAAGhAwEBAAShBAABAAGhBAECoQEKAgAEoQQLAQAEoQMcAaEEFQGhAiECAAShAS4BoQQYAaEEHgGhBB8BoQQdAQABoQQhAaEEIAGhBCMBAAGhBCUCoQQkAqEEKAEABKEEKgGhBCsBoQQwAQABoQQxAaEEMgGhBDQBAAGhBDYBoQQ1AQAEoQQ5AQABoQQ4AQAEoQM6AQAEoQRFAaEESgEAAaEESwEABKEETQGhBEABoQRSAgABoQRTAgAEoQRVAgABoQReAaEEWAEABKEEYAEAAaEEZgKhBGECAAShBGsBAAGhAaUBAgAEoQRwAgABoQRzAQAEoQR5AQABoQSAAQEABKEEggEBAAGhBIcBAwABoQGzAQEAAaEEjQECpwHMAQAIAASRAQR9AX0CfQN9BGcDACEBA21hcAN0d28BoQMAAQAEoQMBAQABoQMGAQAEIQEDbWFwA29uZQOhBAEBAAShAw8CoQMQAaEDFgEAAaEDFwEAAaEDGAGhAxwBAAGhAx0BoQIaBAAEoQMjAgABoQMpAQABoQMfAQABoQMrAQABoQMvAaEDLQEAAaEDMQIABKEDNQGhAzIBoQM6AaEDPAGhBCMBAAGhAU8BAAGhA0ADoQJCAQABoQNEAgABoQNFAgAEoQJEAQABoQNLAaEEQAEABKEESgEAAaEDWAGhA1MBAAGhA1oBAAGhA10DoQNbAQABoQNhAwABoQNiAQAEoQNmAaEDaAWhA20BAAShA3IBAAGhA3MBAAShA3gBoQN6A6EDfwEAAaEDgwEBAAShA4UBAgABoQOLAQGhA4IBAaEDjQEBoQOOAQEAAaEDjwEBAAShA5ABAaEDkgEBoQOXAQEABKEDmQEBAAGhA5gBAQABoQOgAQEAAaEDngEBaQIAIQEDbWFwA3R3bwGhAwABoQEAAQABoQIBAQAEoQIEAaEDAQKhAQwDAAShAg4BAAShAhMBoQQJBAABoQQVAQABoQIeAaECHAGhBBgBAAShAiICAAShBB4BAAShBB8BAAGhAzwBAAGhBCMCoQM9AqEDPgEAAaECOQEABKECPAGhAkEBAAGhAjoBAAGhAkIBAAShAkQBAAShAksBAAGhA0UCAAShAlMCoQJQAQAEoQJZAaECWgEAAaECYAKhAl8BAAShAmMCAAGhAmoBoQJkAgAEoQRSAQABoQJzAQAEoQRTAQAEoQJ6AQAEoQJ1AQABoQKEAQEABKEChgEBoQJ/AgABoQKLAQEABKECjwECAAGhApUBAQABoQKXAQGhAo0BAQABoQKaAQGhApkBAQABoQKcAQEAAaECnwEBAAShAqEBAaECnQEBAAShAqYBAaECpwEBAAGhAqwBAQABoQKtAQEABKECrwECAAF5AQAhAQNtYXADb25lASEBA21hcAN0d28CAAGhAQABoQMAAaEBBAEAAaEBBQGhAQYBoQMPAaEEAQGhAQoBoQELAaEBDAEABKEBDgEAAaEBDQEABKEBFQEABKEDHAKhBBUBAAShASECAAShAScBoQQWAwAEoQIhAgABoQEvAaECIgEABKEBOAEAAaEBNwEAAaEBPwGhAzoBAAShAUIBoQQjAQABoQFIAQABoQFKAaEDPgEAAaECOgEAAaEBTwIAAaEBUgEAAaECQQGhAVQCoQFWAgABoQFYAQAEoQFcAqEBWgEAAaEBYgShAWMBAAGhAWgBAAGhAWkCAAGhAW4BAAGhAWsBAAShAXABAAShAXcCAAShAX0BAAShAYIBAaEBcgEAAaEBhwEBoQGIAQEABKEBigEBAAShAZABAQAEoQGLAQIABKEBlQEBAAShBGkEAAGhAagBAQAEoQRzAQABoQGvAQKhBHsBAAGhAbMBAgAEoQSAAQEAAaEBuwECAAGhAbYBAqEEiwEBAAShAcIBAQAEoQHHAQEABKEEkAEBpwHRAQEoAAHSAQdkZWVwa2V5AXcJZGVlcHZhbHVloQHMAQFiAAAhAQNtYXADb25lAwABoQACASEBA21hcAN0d28CAAShAAQBoQAGAaEACwEAAaEADQIABKEADAEABKEAEAEABKEAGgEABKEAHwEABKEAFQGhACQBAAGhACoBoQApAaEALAGhAC0BoQAuAaEALwEAAaEAMAIAAaEANAEABKEAMQEABKEANgEAAaEAQAIAAaEAOwGhAEMCAAShAEcBAAShAEwBoQBFAQAEoQBRAQAEoQBXAqEAUgEABKEAXgIAAaEAZAKhAF0BoQBnAqEAaAEABKEAawGhAGoCoQBwAQABoQBzAQAEoQB1AQABoQB6AaEAcgGhAHwBAAShAH4BoQB9AgABoQCFAQEABKEAhwEBAAShAIwBAaEAgwEBAAShAJIBAQAEoQCXAQIABKEAkQEBAAGhAJ0BAQAEoQCiAQEABKEApAECAAGhAK8BAqEAqQEBAAGhALMBAQABBQABALcBAQIA0gHUAQEEAQCRAQMBAKUBAgEAuQE=' // eslint-disable-next-line const oldVal = /** @type {any} */ ({"one":[1,2,3,4],"two":{"deepkey":"deepvalue"}}) @@ -34,13 +33,13 @@ export const testMapDecodingCompatibilityV1 = tc => { } /** - * @param {t.TestCase} tc + * @param {t.TestCase} _tc */ -export const testTextDecodingCompatibilityV1 = tc => { +export const testTextDecodingCompatibilityV1 = _tc => { const oldDoc = 'BS8EAAUBBHRleHRveyJpbWFnZSI6Imh0dHBzOi8vdXNlci1pbWFnZXMuZ2l0aHVidXNlcmNvbnRlbnQuY29tLzU1NTM3NTcvNDg5NzUzMDctNjFlZmIxMDAtZjA2ZC0xMWU4LTkxNzctZWU4OTVlNTkxNmU1LnBuZyJ9RAQAATHBBAEEAAHBBAIEAAHEBAMEAAQxdXUKxQQCBANveyJpbWFnZSI6Imh0dHBzOi8vdXNlci1pbWFnZXMuZ2l0aHVidXNlcmNvbnRlbnQuY29tLzU1NTM3NTcvNDg5NzUzMDctNjFlZmIxMDAtZjA2ZC0xMWU4LTkxNzctZWU4OTVlNTkxNmU1LnBuZyJ9xQMJBAFveyJpbWFnZSI6Imh0dHBzOi8vdXNlci1pbWFnZXMuZ2l0aHVidXNlcmNvbnRlbnQuY29tLzU1NTM3NTcvNDg5NzUzMDctNjFlZmIxMDAtZjA2ZC0xMWU4LTkxNzctZWU4OTVlNTkxNmU1LnBuZyJ9xQMJBAlveyJpbWFnZSI6Imh0dHBzOi8vdXNlci1pbWFnZXMuZ2l0aHVidXNlcmNvbnRlbnQuY29tLzU1NTM3NTcvNDg5NzUzMDctNjFlZmIxMDAtZjA2ZC0xMWU4LTkxNzctZWU4OTVlNTkxNmU1LnBuZyJ9xgMBAwIGaXRhbGljBHRydWXGBAsDAgVjb2xvcgYiIzg4OCLEBAwDAgExxAQNAwIBMsEEDgMCAsYEEAMCBml0YWxpYwRudWxsxgQRAwIFY29sb3IEbnVsbMQDAQQLATHEBBMECwIyOcQEFQQLCzl6anpueXdvaHB4xAQgBAsIY25icmNhcQrBAxADEQHGAR8BIARib2xkBHRydWXGAgACAQRib2xkBG51bGzFAwkECm97ImltYWdlIjoiaHR0cHM6Ly91c2VyLWltYWdlcy5naXRodWJ1c2VyY29udGVudC5jb20vNTU1Mzc1Ny80ODk3NTMwNy02MWVmYjEwMC1mMDZkLTExZTgtOTE3Ny1lZTg5NWU1OTE2ZTUucG5nIn3GARABEQZpdGFsaWMEdHJ1ZcYELQERBWNvbG9yBiIjODg4IsYBEgETBml0YWxpYwRudWxsxgQvARMFY29sb3IEbnVsbMYCKwIsBGJvbGQEdHJ1ZcYCLQIuBGJvbGQEbnVsbMYCjAECjQEGaXRhbGljBHRydWXGAo4BAo8BBml0YWxpYwRudWxswQA2ADcBxgQ1ADcFY29sb3IGIiM4ODgixgNlA2YFY29sb3IEbnVsbMYDUwNUBGJvbGQEdHJ1ZcQEOANUFjEzMTZ6bHBrbWN0b3FvbWdmdGhicGfGBE4DVARib2xkBG51bGzGAk0CTgZpdGFsaWMEdHJ1ZcYEUAJOBWNvbG9yBiIjODg4IsYCTwJQBml0YWxpYwRudWxsxgRSAlAFY29sb3IEbnVsbMYChAEChQEGaXRhbGljBHRydWXGBFQChQEFY29sb3IGIiM4ODgixgKGAQKHAQZpdGFsaWMEbnVsbMYEVgKHAQVjb2xvcgRudWxsxAMpAyoRMTMyMWFwZ2l2eWRxc2pmc2XFBBIDAm97ImltYWdlIjoiaHR0cHM6Ly91c2VyLWltYWdlcy5naXRodWJ1c2VyY29udGVudC5jb20vNTU1Mzc1Ny80ODk3NTMwNy02MWVmYjEwMC1mMDZkLTExZTgtOTE3Ny1lZTg5NWU1OTE2ZTUucG5nIn0zAwAEAQR0ZXh0AjEyhAMBAzkwboQDBAF4gQMFAoQDBwJyCsQDBAMFBjEyOTd6bcQDDwMFAXbEAxADBQFwwQMRAwUBxAMSAwUFa3pxY2rEAxcDBQJzYcQDGQMFBHNqeQrBAxIDEwHBAAwAEAHEAA0ADgkxMzAyeGNpd2HEAygADgF5xAMpAA4KaGhlenVraXF0dMQDMwAOBWhudGsKxgMoAykEYm9sZAR0cnVlxAM5AykGMTMwNXJswQM/AykCxANBAykDZXlrxgNEAykEYm9sZARudWxsxAMzAzQJMTMwN3R2amllwQNOAzQCxANQAzQDamxoxANTAzQCZ3bEA1UDNAJsYsQDVwM0AmYKxgNBA0IEYm9sZARudWxswQNaA0ICxANcA0ICMDjBA14DQgLEA2ADQgEKxgNhA0IEYm9sZAR0cnVlxQIaAhtveyJpbWFnZSI6Imh0dHBzOi8vdXNlci1pbWFnZXMuZ2l0aHVidXNlcmNvbnRlbnQuY29tLzU1NTM3NTcvNDg5NzUzMDctNjFlZmIxMDAtZjA2ZC0xMWU4LTkxNzctZWU4OTVlNTkxNmU1LnBuZyJ9wQA3ADgCwQNlADgBxANmADgKMTVteml3YWJ6a8EDcAA4AsQDcgA4BnJybXNjdsEDeAA4AcQCYgJjATHEA3oCYwIzMsQDfAJjCTRyb3J5d3RoccQDhQECYwEKxAOFAQOGARkxMzI1aW9kYnppenhobWxpYnZweXJ4bXEKwQN6A3sBxgOgAQN7BWNvbG9yBiIjODg4IsYDfAN9Bml0YWxpYwRudWxsxgOiAQN9BWNvbG9yBG51bGxSAgAEAQR0ZXh0ATGEAgACMjiEAgIBOYECAwKEAgUBdYQCBgJ0Y4QCCAJqZYECCgKEAgwBaoECDQGBAg4BhAIPAnVmhAIRAQrEAg4CDwgxMjkycXJtZsQCGgIPAmsKxgIGAgcGaXRhbGljBHRydWXGAggCCQZpdGFsaWMEbnVsbMYCEQISBml0YWxpYwR0cnVlxAIfAhIBMcECIAISAsQCIgISAzRoc8QCJQISAXrGAiYCEgZpdGFsaWMEbnVsbMEAFQAWAsQCKQAWATDEAioAFgEwxAIrABYCaHjEAi0AFglvamVldHJqaHjBAjYAFgLEAjgAFgJrcsQCOgAWAXHBAjsAFgHBAjwAFgHEAj0AFgFuxAI+ABYCZQrGAiUCJgZpdGFsaWMEbnVsbMQCQQImAjEzwQJDAiYCxAJFAiYIZGNjeGR5eGfEAk0CJgJ6Y8QCTwImA2Fwb8QCUgImAnRuxAJUAiYBcsQCVQImAmduwQJXAiYCxAJZAiYBCsYCWgImBml0YWxpYwR0cnVlxAI6AjsEMTMwM8QCXwI7A3VodsQCYgI7BmdhbmxuCsUCVQJWb3siaW1hZ2UiOiJodHRwczovL3VzZXItaW1hZ2VzLmdpdGh1YnVzZXJjb250ZW50LmNvbS81NTUzNzU3LzQ4OTc1MzA3LTYxZWZiMTAwLWYwNmQtMTFlOC05MTc3LWVlODk1ZTU5MTZlNS5wbmcifcECPAI9AcECPgI/AcYDFwMYBml0YWxpYwR0cnVlxgJsAxgFY29sb3IGIiM4ODgixgMZAxoGaXRhbGljBG51bGzGAm4DGgVjb2xvcgRudWxswQMQBCkBxAJwBCkKMTMwOXpsZ3ZqeMQCegQpAWfBAnsEKQLGBA0EDgZpdGFsaWMEbnVsbMYCfgQOBWNvbG9yBG51bGzEAn8EDgUxMzEwZ8QChAEEDgJ3c8QChgEEDgZoeHd5Y2jEAowBBA4Ca3HEAo4BBA4Ec2RydcQCkgEEDgRqcWljwQKWAQQOBMQCmgEEDgEKxgKbAQQOBml0YWxpYwR0cnVlxgKcAQQOBWNvbG9yBiIjODg4IsECaAI7AcQCCgEBFjEzMThqd3NramFiZG5kcmRsbWphZQrGA1UDVgRib2xkBHRydWXGA1cDWARib2xkBG51bGzGAEAAQQZpdGFsaWMEdHJ1ZcYCtwEAQQRib2xkBG51bGzEArgBAEESMTMyNnJwY3pucWFob3BjcnRkxgLKAQBBBml0YWxpYwRudWxsxgLLAQBBBGJvbGQEdHJ1ZRkBAMUCAgIDb3siaW1hZ2UiOiJodHRwczovL3VzZXItaW1hZ2VzLmdpdGh1YnVzZXJjb250ZW50LmNvbS81NTUzNzU3LzQ4OTc1MzA3LTYxZWZiMTAwLWYwNmQtMTFlOC05MTc3LWVlODk1ZTU5MTZlNS5wbmcifcQCCgILBzEyOTN0agrGABgAGQRib2xkBHRydWXGAA0ADgRib2xkBG51bGxEAgAHMTMwNnJ1cMQBEAIAAnVqxAESAgANaWtrY2pucmNwc2Nrd8QBHwIAAQrFBBMEFG97ImltYWdlIjoiaHR0cHM6Ly91c2VyLWltYWdlcy5naXRodWJ1c2VyY29udGVudC5jb20vNTU1Mzc1Ny80ODk3NTMwNy02MWVmYjEwMC1mMDZkLTExZTgtOTE3Ny1lZTg5NWU1OTE2ZTUucG5nIn3FAx0DBW97ImltYWdlIjoiaHR0cHM6Ly91c2VyLWltYWdlcy5naXRodWJ1c2VyY29udGVudC5jb20vNTU1Mzc1Ny80ODk3NTMwNy02MWVmYjEwMC1mMDZkLTExZTgtOTE3Ny1lZTg5NWU1OTE2ZTUucG5nIn3GAlICUwRib2xkBHRydWXGAlQCVQRib2xkBG51bGzGAnsCfAZpdGFsaWMEdHJ1ZcYBJQJ8BWNvbG9yBiIjODg4IsYBJgJ8BGJvbGQEbnVsbMQBJwJ8CjEzMTRweWNhdnXGATECfAZpdGFsaWMEbnVsbMYBMgJ8BWNvbG9yBG51bGzBATMCfAHFADEAMm97ImltYWdlIjoiaHR0cHM6Ly91c2VyLWltYWdlcy5naXRodWJ1c2VyY29udGVudC5jb20vNTU1Mzc1Ny80ODk3NTMwNy02MWVmYjEwMC1mMDZkLTExZTgtOTE3Ny1lZTg5NWU1OTE2ZTUucG5nIn3GADUANgZpdGFsaWMEdHJ1ZcEANwA4AcQAMgAzEzEzMjJybmJhb2tvcml4ZW52cArEAgUCBhcxMzIzbnVjdnhzcWx6bndsZmF2bXBjCsYDDwMQBGJvbGQEdHJ1ZR0AAMQEAwQEDTEyOTVxZnJ2bHlmYXDEAAwEBAFjxAANBAQCanbBAAwADQHEABAADQEywQARAA0ExAAVAA0DZHZmxAAYAA0BYcYCAwIEBml0YWxpYwR0cnVlwQAaAgQCxAAcAgQEMDRrdcYAIAIEBml0YWxpYwRudWxsxQQgBCFveyJpbWFnZSI6Imh0dHBzOi8vdXNlci1pbWFnZXMuZ2l0aHVidXNlcmNvbnRlbnQuY29tLzU1NTM3NTcvNDg5NzUzMDctNjFlZmIxMDAtZjA2ZC0xMWU4LTkxNzctZWU4OTVlNTkxNmU1LnBuZyJ9xQJAABZveyJpbWFnZSI6Imh0dHBzOi8vdXNlci1pbWFnZXMuZ2l0aHVidXNlcmNvbnRlbnQuY29tLzU1NTM3NTcvNDg5NzUzMDctNjFlZmIxMDAtZjA2ZC0xMWU4LTkxNzctZWU4OTVlNTkxNmU1LnBuZyJ9xAQVBBYGMTMxMWtrxAIqAisIMTMxMnFyd3TEADECKwFixAAyAisDcnhxxAA1AisBasQANgIrAXjEADcCKwZkb3ZhbwrEAgAEKwMxMzHEAEAEKwkzYXhoa3RoaHXGAnoCewRib2xkBG51bGzFAEoCe297ImltYWdlIjoiaHR0cHM6Ly91c2VyLWltYWdlcy5naXRodWJ1c2VyY29udGVudC5jb20vNTU1Mzc1Ny80ODk3NTMwNy02MWVmYjEwMC1mMDZkLTExZTgtOTE3Ny1lZTg5NWU1OTE2ZTUucG5nIn3GAEsCewRib2xkBHRydWXEAl8CYBExMzE3cGZjeWhrc3JrcGt0CsQBHwQqCzEzMTliY2Nna3AKxAKSAQKTARUxMzIwY29oYnZjcmtycGpuZ2RvYwoFBAQCAg8CKQE1AQADEAESBBsCAwsGAhIBHgJAAk8CWwJfAmQDcQJ5AaABAQIOBAILAg4CIQIoAjcCPAJEAlgCagJwAXwClwEEngEBAQI0ATcB' // eslint-disable-next-line - const oldVal = [{"insert":"1306rup"},{"insert":"uj","attributes":{"italic":true,"color":"#888"}},{"insert":"ikkcjnrcpsckw1319bccgkp\n"},{"insert":"\n1131","attributes":{"bold":true}},{"insert":"1326rpcznqahopcrtd","attributes":{"italic":true}},{"insert":"3axhkthhu","attributes":{"bold":true}},{"insert":"28"},{"insert":{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}},{"insert":"9"},{"insert":"04ku","attributes":{"italic":true}},{"insert":"1323nucvxsqlznwlfavmpc\nu"},{"insert":"tc","attributes":{"italic":true}},{"insert":"je1318jwskjabdndrdlmjae\n1293tj\nj1292qrmf"},{"insert":{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}},{"insert":"k\nuf"},{"insert":"14hs","attributes":{"italic":true}},{"insert":"13dccxdyxg"},{"insert":"zc","attributes":{"italic":true,"color":"#888"}},{"insert":"apo"},{"insert":"tn","attributes":{"bold":true}},{"insert":"r"},{"insert":{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}},{"insert":"gn\n"},{"insert":"z","attributes":{"italic":true}},{"insert":"\n121"},{"insert":{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}},{"insert":"291311kk9zjznywohpx"},{"insert":{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}},{"insert":"cnbrcaq\n"},{"insert":"1","attributes":{"italic":true,"color":"#888"}},{"insert":"1310g"},{"insert":"ws","attributes":{"italic":true,"color":"#888"}},{"insert":"hxwych"},{"insert":"kq","attributes":{"italic":true}},{"insert":"sdru1320cohbvcrkrpjngdoc\njqic\n"},{"insert":"2","attributes":{"italic":true,"color":"#888"}},{"insert":{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}},{"insert":"90n1297zm"},{"insert":"v1309zlgvjx","attributes":{"bold":true}},{"insert":{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}},{"insert":"g","attributes":{"bold":true}},{"insert":"1314pycavu","attributes":{"italic":true,"color":"#888"}},{"insert":"pkzqcj"},{"insert":"sa","attributes":{"italic":true,"color":"#888"}},{"insert":"sjy\n"},{"insert":{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}},{"insert":"xr\n"},{"insert":{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}},{"insert":{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}},{"insert":{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}},{"insert":"1"},{"insert":{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}},{"insert":"1295qfrvlyfap201312qrwt"},{"insert":{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}},{"insert":"b1322rnbaokorixenvp\nrxq"},{"insert":"j","attributes":{"italic":true}},{"insert":"x","attributes":{"italic":true,"color":"#888"}},{"insert":"15mziwabzkrrmscvdovao\n0","attributes":{"italic":true}},{"insert":"hx","attributes":{"italic":true,"bold":true}},{"insert":"ojeetrjhxkr13031317pfcyhksrkpkt\nuhv1","attributes":{"italic":true}},{"insert":"32","attributes":{"italic":true,"color":"#888"}},{"insert":"4rorywthq1325iodbzizxhmlibvpyrxmq\n\nganln\nqne\n"},{"insert":{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}},{"insert":"dvf"},{"insert":"ac","attributes":{"bold":true}},{"insert":"1302xciwa"},{"insert":"1305rl","attributes":{"bold":true}},{"insert":"08\n"},{"insert":"eyk","attributes":{"bold":true}},{"insert":"y1321apgivydqsjfsehhezukiqtt1307tvjiejlh"},{"insert":"1316zlpkmctoqomgfthbpg","attributes":{"bold":true}},{"insert":"gv"},{"insert":"lb","attributes":{"bold":true}},{"insert":"f\nhntk\njv1uu\n"},{"insert":{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}}] + const oldVal = [{"insert":"1306rup"},{"insert":"uj","format":{"italic":true,"color":"#888"}},{"insert":"ikkcjnrcpsckw1319bccgkp\n"},{"insert":"\n1131","format":{"bold":true}},{"insert":"1326rpcznqahopcrtd","format":{"italic":true}},{"insert":"3axhkthhu","format":{"bold":true}},{"insert":"28"},{"insert":[{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}]},{"insert":"9"},{"insert":"04ku","format":{"italic":true}},{"insert":"1323nucvxsqlznwlfavmpc\nu"},{"insert":"tc","format":{"italic":true}},{"insert":"je1318jwskjabdndrdlmjae\n1293tj\nj1292qrmf"},{"insert":[{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}]},{"insert":"k\nuf"},{"insert":"14hs","format":{"italic":true}},{"insert":"13dccxdyxg"},{"insert":"zc","format":{"italic":true,"color":"#888"}},{"insert":"apo"},{"insert":"tn","format":{"bold":true}},{"insert":"r"},{"insert":[{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}]},{"insert":"gn\n"},{"insert":"z","format":{"italic":true}},{"insert":"\n121"},{"insert":[{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}]},{"insert":"291311kk9zjznywohpx"},{"insert":[{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}]},{"insert":"cnbrcaq\n"},{"insert":"1","format":{"italic":true,"color":"#888"}},{"insert":"1310g"},{"insert":"ws","format":{"italic":true,"color":"#888"}},{"insert":"hxwych"},{"insert":"kq","format":{"italic":true}},{"insert":"sdru1320cohbvcrkrpjngdoc\njqic\n"},{"insert":"2","format":{"italic":true,"color":"#888"}},{"insert":[{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}]},{"insert":"90n1297zm"},{"insert":"v1309zlgvjx","format":{"bold":true}},{"insert":[{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}]},{"insert":"g","format":{"bold":true}},{"insert":"1314pycavu","format":{"italic":true,"color":"#888"}},{"insert":"pkzqcj"},{"insert":"sa","format":{"italic":true,"color":"#888"}},{"insert":"sjy\n"},{"insert":[{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}]},{"insert":"xr\n"},{"insert":[{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"},{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}, {"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}]},{"insert":"1"},{"insert":[{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}]},{"insert":"1295qfrvlyfap201312qrwt"},{"insert":[{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}]},{"insert":"b1322rnbaokorixenvp\nrxq"},{"insert":"j","format":{"italic":true}},{"insert":"x","format":{"italic":true,"color":"#888"}},{"insert":"15mziwabzkrrmscvdovao\n0","format":{"italic":true}},{"insert":"hx","format":{"italic":true,"bold":true}},{"insert":"ojeetrjhxkr13031317pfcyhksrkpkt\nuhv1","format":{"italic":true}},{"insert":"32","format":{"italic":true,"color":"#888"}},{"insert":"4rorywthq1325iodbzizxhmlibvpyrxmq\n\nganln\nqne\n"},{"insert":[{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}]},{"insert":"dvf"},{"insert":"ac","format":{"bold":true}},{"insert":"1302xciwa"},{"insert":"1305rl","format":{"bold":true}},{"insert":"08\n"},{"insert":"eyk","format":{"bold":true}},{"insert":"y1321apgivydqsjfsehhezukiqtt1307tvjiejlh"},{"insert":"1316zlpkmctoqomgfthbpg","format":{"bold":true}},{"insert":"gv"},{"insert":"lb","format":{"bold":true}},{"insert":"f\nhntk\njv1uu\n"},{"insert":[{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}]}].map(x => ({ type: 'insert', ...x })) const doc = new Y.Doc() Y.applyUpdate(doc, buffer.fromBase64(oldDoc)) - t.compare(doc.getText('text').toDelta(), oldVal) + t.compare(doc.getText('text').getContent().toJSON().children, /** @type {any} */ (oldVal)) } diff --git a/tests/delta.tests.js b/tests/delta.tests.js new file mode 100644 index 000000000..7063df3a2 --- /dev/null +++ b/tests/delta.tests.js @@ -0,0 +1,195 @@ +import * as Y from '../src/index.js' +import * as delta from 'lib0/delta' +import * as t from 'lib0/testing' +import * as s from 'lib0/schema' + +/** + * Delta is a versatyle format enabling you to efficiently describe changes. It is part of lib0, so + * that non-yjs applications can use it without consuming the full Yjs package. It is well suited + * for efficiently describing state & changesets. + * + * Assume we start with the text "hello world". Now we want to delete " world" and add an + * exclamation mark. The final content should be "hello!" ("hello world" => "hello!") + * + * In most editors, you would describe the necessary changes as replace operations using indexes. + * However, this might become ambiguous when many changes are involved. + * + * - delete range 5-11 + * - insert "!" at position 11 + * + * Using the delta format, you can describe the changes similar to what you would do in an text editor. + * The "|" describes the current cursor position. + * + * - d.retain(5) - "|hello world" => "hello| world" - jump over the next five characters + * - d.delete(6) - "hello| world" => "hello|" - delete the next 6 characres + * - d.insert('!') - "hello!|" - insert "!" at the current position + * => compact form: d.retain(5).delete(6).insert('!') + * + * You can also apply the changes in two distinct steps and then rebase the op so that you can apply + * them in two distinct steps. + * - delete " world": d1 = delta.create().retain(5).delete(6) + * - insert "!": d2 = delta.create().retain(11).insert('!') + * - rebase d2 on-top of d1: d2.rebase(d1) == delta.create().retain(5).insert('!') + * - merge into a single change: d1.apply(d2) == delta.create().retain(5).delete(6).insert(!) + * + * @param {t.TestCase} _tc + */ +export const testDeltaBasics = _tc => { + // the state of our text document + const state = delta.create().insert('hello world') + // describe changes: delete " world" & insert "!" + const change = delta.create().retain(5).delete(6).insert('!') + // apply changes to state + state.apply(change) + // compare state to expected state + t.assert(state.equals(delta.create().insert('hello!'))) +} + +/** + * lib0 also ships a schema library that can be used to validate JSON objects and custom data types, + * like Yjs types. + * + * As a convention, schemas are usually prefixed with a $ sign. This clarifies the difference + * between a schema, and an instance of a schema. + * + * const $myobj = s.$object({ key: s.$number }) + * let inputValue: any + * if ($myobj.check(inputValue)) { + * inputValue // is validated and of type $myobj + * } + * + * We can also define the expected values on a delta. + * + * @param {t.TestCase} _tc + */ +export const testDeltaBasicSchema = _tc => { + const $d = delta.$delta({ attrs: s.$object({ key: s.$string }), children: s.$number, hasText: false }) + const d = delta.create($d) + // @ts-expect-error + d.set('key', false) // invalid change: will throw a type error + t.fails(() => { + // @ts-expect-error + d.apply(delta.create().set('key', false)) // invalid delta: will throw a type error + }) +} + +/** + * Deltas can describe changes on attributes and children. Textual insertions are children. But we + * may also insert json-objects and other deltas as children. + * Key-value pairs can be represented as attributes. This "convoluted" changeset enables us to + * describe many changes in the same breath: + * + * delta.create().set('a', 42).retain(5).delete(6).insert('!').unset('b') + * + * @param {t.TestCase} _tc + */ +export const testDeltaValues = _tc => { + const change = delta.create().set('a', 42).unset('b').retain(5).delete(6).insert('!').insert([{ my: 'custom object' }]) + // iterate through attribute changes + for (const attrChange of change.attrs) { + if (delta.$insertOp.check(attrChange)) { + console.log(`set ${attrChange.key} to ${attrChange.value}`) + } else if (delta.$deleteOp.check(attrChange)) { + console.log(`delete ${attrChange.key}`) + } + } + // iterate through child changes + for (const childChange of change.children) { + if (delta.$retainOp.check(childChange)) { + console.log(`retain ${childChange.retain} child items`) + } else if (delta.$deleteOp.check(childChange)) { + console.log(`delete ${childChange.delete} child items`) + } else if (delta.$insertOp.check(childChange)) { + console.log('insert child items:', childChange.insert) + } else if (delta.$textOp.check(childChange)) { + console.log('insert textual content', childChange.insert) + } + } +} + +/** + * The new delta defines changes on attributes (key-value) and child elements (list & text), but can + * also be used to describe the current state of a document. + * + * 1. apply a delta to change a yjs type + * 2. observe deltas to read the differences + * 3. merge deltas to reflect multiple changes in a single delta + * 4. All Yjs types fully support the delta format. It is no longer necessary to define the type (such as Y.Array) + * + * @param {t.TestCase} _tc + */ +export const testBasics = _tc => { + const ydoc = new Y.Doc() + const ytype = ydoc.get('my data') + /** + * @type {delta.Delta} + */ + let observedDelta = delta.create() + ytype.observe(event => { + observedDelta = event.deltaDeep + console.log('ytype changed:', observedDelta.toJSON()) + }) + // define a change: set attribute: a=42 + const attrChange = delta.create().set('a', 42).done() + // define a change: insert textual content and an object + const childChange = delta.create().insert('hello').insert([{ my: 'object' }]).done() + // merge changes + const mergedChanges = delta.create(delta.$deltaAny).apply(attrChange).apply(childChange).done() + console.log('merged changes: ', mergedChanges.toJSON()) + ytype.applyDelta(mergedChanges) + // the observed change should equal the applied change + t.assert(observedDelta.equals(mergedChanges)) + // read the current state of the yjs types as a delta + const currState = ytype.getContentDeep() + t.assert(currState.equals(mergedChanges)) // equal to the changes that we applied +} + +/** + * Deltas allow us to describe the differences between two Yjs documents though "Attributions". + * + * - We can attribute changes to a user, or a group of users + * - There are 'insert', 'delete', and 'format' attributions + * - When we render attributions, we render inserted & deleted content as an insertions with special + * attributes which allow you to.. + * -- Render deleted content using a strikethrough: I.e. `hello w̶o̶r̶l̶d̶!` + * -- Render attributed insertions using a background color. + * + * @param {t.TestCase} _tc + */ +export const testAttributions = _tc => { + const ydocV1 = new Y.Doc() + const ytypeV1 = ydocV1.get('txt') + ytypeV1.applyDelta(delta.create().insert('hello world')) + // create a new version with updated content + const ydoc = new Y.Doc() + Y.applyUpdate(ydoc, Y.encodeStateAsUpdate(ydocV1)) + const ytype = ydoc.get('txt') + // delete " world" and insert exclamation mark "!". + ytype.applyDelta(delta.create().retain(5).delete(6).insert('!')) + const am = Y.createAttributionManagerFromDiff(ydocV1, ydoc) + // get the attributed differences + const attributedContent = ytype.getContent(am) + console.log('attributed content', attributedContent.toJSON()) + t.assert(attributedContent.equals(delta.create().insert('hello').insert(' world', null, { delete: [] }).insert('!', null, { insert: [] }))) + // for editor bindings, it is also necessary to observe changes and get the attributed changes + ytype.observe(event => { + const attributedChange = event.getDelta(am) + console.log('the attributed change', attributedChange.toJSON()) + t.assert(attributedChange.equals(delta.create().retain(11).insert('!', null, { insert: [] }))) + const unattributedChange = event.delta + console.log('the UNattributed change', unattributedChange.toJSON()) + t.assert(unattributedChange.equals(delta.create().retain(5).insert('!'))) + }) + /** + * Content now has different representations. + * - The UNattributed representation renders the latest state, without history. + * - The attributed representation renders the differences. + * + * Attributed: 'hello world!' + * UNattributed: 'world!' + */ + // Apply a change to the attributed content + ytype.applyDelta(delta.create().retain(11).insert('!'), am) + // // Equivalent to applying a change to the UNattributed content: + // ytype.applyDelta(delta.create().retain(5).insert('!')) +} diff --git a/tests/doc.tests.js b/tests/doc.tests.js index 994ecaebd..0381a00f5 100644 --- a/tests/doc.tests.js +++ b/tests/doc.tests.js @@ -1,13 +1,78 @@ - import * as Y from '../src/index.js' import * as t from 'lib0/testing' +/** + * @param {t.TestCase} _tc + */ +export const testAfterTransactionRecursion = _tc => { + const ydoc = new Y.Doc() + const yxml = ydoc.getXmlFragment('') + ydoc.on('afterTransaction', tr => { + if (tr.origin === 'test') { + yxml.toJSON() + } + }) + ydoc.transact(_tr => { + for (let i = 0; i < 15000; i++) { + yxml.push([new Y.XmlText('a')]) + } + }, 'test') +} + +/** + * @param {t.TestCase} _tc + */ +export const testFindTypeInOtherDoc = _tc => { + const ydoc = new Y.Doc() + const ymap = ydoc.getMap() + const ytext = ymap.set('ytext', new Y.Text()) + const ydocClone = new Y.Doc() + Y.applyUpdate(ydocClone, Y.encodeStateAsUpdate(ydoc)) + /** + * @template {import('../src/utils/types.js').YType} Type + * @param {Type} ytype + * @param {Y.Doc} otherYdoc + * @return {Type} + */ + const findTypeInOtherYdoc = (ytype, otherYdoc) => { + const ydoc = /** @type {Y.Doc} */ (ytype.doc) + if (ytype._item === null) { + /** + * If is a root type, we need to find the root key in the original ydoc + * and use it to get the type in the other ydoc. + */ + const rootKey = Array.from(ydoc.share.keys()).find( + (key) => ydoc.share.get(key) === ytype + ) + if (rootKey == null) { + throw new Error('type does not exist in other ydoc') + } + return /** @type {Type} */ (otherYdoc.get(rootKey, /** @type {import('../src/utils/types.js').YTypeConstructors} */ (ytype.constructor))) + } else { + /** + * If it is a sub type, we use the item id to find the history type. + */ + const ytypeItem = ytype._item + const otherStructs = otherYdoc.store.clients.get(ytypeItem.id.client) ?? [] + const itemIndex = Y.findIndexSS( + otherStructs, + ytypeItem.id.clock + ) + const otherItem = /** @type {Y.Item} */ (otherStructs[itemIndex]) + const otherContent = /** @type {Y.ContentType} */ (otherItem.content) + return /** @type {Type} */ (otherContent.type) + } + } + t.assert(findTypeInOtherYdoc(ymap, ydocClone) != null) + t.assert(findTypeInOtherYdoc(ytext, ydocClone) != null) +} + /** * Client id should be changed when an instance receives updates from another client using the same client id. * - * @param {t.TestCase} tc + * @param {t.TestCase} _tc */ -export const testClientIdDuplicateChange = tc => { +export const testClientIdDuplicateChange = _tc => { const doc1 = new Y.Doc() doc1.clientID = 0 const doc2 = new Y.Doc() @@ -19,9 +84,9 @@ export const testClientIdDuplicateChange = tc => { } /** - * @param {t.TestCase} tc + * @param {t.TestCase} _tc */ -export const testGetTypeEmptyId = tc => { +export const testGetTypeEmptyId = _tc => { const doc1 = new Y.Doc() doc1.getText('').insert(0, 'h') doc1.getText().insert(1, 'i') @@ -32,9 +97,9 @@ export const testGetTypeEmptyId = tc => { } /** - * @param {t.TestCase} tc + * @param {t.TestCase} _tc */ -export const testToJSON = tc => { +export const testToJSON = _tc => { const doc = new Y.Doc() t.compare(doc.toJSON(), {}, 'doc.toJSON yields empty object') @@ -59,9 +124,9 @@ export const testToJSON = tc => { } /** - * @param {t.TestCase} tc + * @param {t.TestCase} _tc */ -export const testSubdoc = tc => { +export const testSubdoc = _tc => { const doc = new Y.Doc() doc.load() // doesn't do anything { @@ -126,9 +191,9 @@ export const testSubdoc = tc => { } /** - * @param {t.TestCase} tc + * @param {t.TestCase} _tc */ -export const testSubdocLoadEdgeCases = tc => { +export const testSubdocLoadEdgeCases = _tc => { const ydoc = new Y.Doc() const yarray = ydoc.getArray() const subdoc1 = new Y.Doc() @@ -173,9 +238,9 @@ export const testSubdocLoadEdgeCases = tc => { } /** - * @param {t.TestCase} tc + * @param {t.TestCase} _tc */ -export const testSubdocLoadEdgeCasesAutoload = tc => { +export const testSubdocLoadEdgeCasesAutoload = _tc => { const ydoc = new Y.Doc() const yarray = ydoc.getArray() const subdoc1 = new Y.Doc({ autoLoad: true }) @@ -215,9 +280,9 @@ export const testSubdocLoadEdgeCasesAutoload = tc => { } /** - * @param {t.TestCase} tc + * @param {t.TestCase} _tc */ -export const testSubdocsUndo = tc => { +export const testSubdocsUndo = _tc => { const ydoc = new Y.Doc() const elems = ydoc.getXmlFragment() const undoManager = new Y.UndoManager(elems) @@ -230,9 +295,9 @@ export const testSubdocsUndo = tc => { } /** - * @param {t.TestCase} tc + * @param {t.TestCase} _tc */ -export const testLoadDocs = async tc => { +export const testLoadDocsEvent = async _tc => { const ydoc = new Y.Doc() t.assert(ydoc.isLoaded === false) let loadedEvent = false @@ -244,3 +309,44 @@ export const testLoadDocs = async tc => { t.assert(loadedEvent) t.assert(ydoc.isLoaded) } + +/** + * @param {t.TestCase} _tc + */ +export const testSyncDocsEvent = async _tc => { + const ydoc = new Y.Doc() + t.assert(ydoc.isLoaded === false) + t.assert(ydoc.isSynced === false) + let loadedEvent = false + ydoc.once('load', () => { + loadedEvent = true + }) + let syncedEvent = false + ydoc.once('sync', /** @param {any} isSynced */ (isSynced) => { + syncedEvent = true + t.assert(isSynced) + }) + ydoc.emit('sync', [true, ydoc]) + await ydoc.whenLoaded + const oldWhenSynced = ydoc.whenSynced + await ydoc.whenSynced + t.assert(loadedEvent) + t.assert(syncedEvent) + t.assert(ydoc.isLoaded) + t.assert(ydoc.isSynced) + let loadedEvent2 = false + ydoc.on('load', () => { + loadedEvent2 = true + }) + let syncedEvent2 = false + ydoc.on('sync', (isSynced) => { + syncedEvent2 = true + t.assert(isSynced === false) + }) + ydoc.emit('sync', [false, ydoc]) + t.assert(!loadedEvent2) + t.assert(syncedEvent2) + t.assert(ydoc.isLoaded) + t.assert(!ydoc.isSynced) + t.assert(ydoc.whenSynced !== oldWhenSynced) +} diff --git a/tests/encoding.tests.js b/tests/encoding.tests.js index 0f6db1ade..1121b0862 100644 --- a/tests/encoding.tests.js +++ b/tests/encoding.tests.js @@ -1,5 +1,4 @@ import * as t from 'lib0/testing' -import * as promise from 'lib0/promise' import { contentRefs, @@ -11,11 +10,7 @@ import { readContentType, readContentFormat, readContentAny, - readContentDoc, - Doc, - PermanentUserData, - encodeStateAsUpdate, - applyUpdate + readContentDoc } from '../src/internals.js' import * as Y from '../src/index.js' @@ -37,34 +32,6 @@ export const testStructReferences = tc => { // contentRefs[10] is reserved for Skip structs } -/** - * There is some custom encoding/decoding happening in PermanentUserData. - * This is why it landed here. - * - * @param {t.TestCase} tc - */ -export const testPermanentUserData = async tc => { - const ydoc1 = new Doc() - const ydoc2 = new Doc() - const pd1 = new PermanentUserData(ydoc1) - const pd2 = new PermanentUserData(ydoc2) - pd1.setUserMapping(ydoc1, ydoc1.clientID, 'user a') - pd2.setUserMapping(ydoc2, ydoc2.clientID, 'user b') - ydoc1.getText().insert(0, 'xhi') - ydoc1.getText().delete(0, 1) - ydoc2.getText().insert(0, 'hxxi') - ydoc2.getText().delete(1, 2) - await promise.wait(10) - applyUpdate(ydoc2, encodeStateAsUpdate(ydoc1)) - applyUpdate(ydoc1, encodeStateAsUpdate(ydoc2)) - - // now sync a third doc with same name as doc1 and then create PermanentUserData - const ydoc3 = new Doc() - applyUpdate(ydoc3, encodeStateAsUpdate(ydoc1)) - const pd3 = new PermanentUserData(ydoc3) - pd3.setUserMapping(ydoc3, ydoc3.clientID, 'user a') -} - /** * Reported here: https://github.com/yjs/yjs/issues/308 * @param {t.TestCase} tc diff --git a/tests/index.js b/tests/index.js index 2f8d8874a..83b536e04 100644 --- a/tests/index.js +++ b/tests/index.js @@ -1,3 +1,4 @@ +/* eslint-env node */ import * as map from './y-map.tests.js' import * as array from './y-array.tests.js' @@ -10,6 +11,10 @@ import * as doc from './doc.tests.js' import * as snapshot from './snapshot.tests.js' import * as updates from './updates.tests.js' import * as relativePositions from './relativePositions.tests.js' +import * as idset from './IdSet.tests.js' +import * as idmap from './IdMap.tests.js' +import * as attribution from './attribution.tests.js' +import * as delta from './delta.tests.js' import { runTests } from 'lib0/testing' import { isBrowser, isNode } from 'lib0/environment' @@ -18,11 +23,16 @@ import * as log from 'lib0/logging' if (isBrowser) { log.createVConsole(document.body) } -runTests({ - doc, map, array, text, xml, encoding, undoredo, compatibility, snapshot, updates, relativePositions -}).then(success => { + +const tests = { + doc, map, array, text, xml, encoding, undoredo, compatibility, snapshot, updates, relativePositions, idset, idmap, attribution, delta +} + +const run = async () => { + const success = await runTests(tests) /* istanbul ignore next */ if (isNode) { process.exit(success ? 0 : 1) } -}) +} +run() diff --git a/tests/relativePositions.tests.js b/tests/relativePositions.tests.js index 817b98052..75e7088d8 100644 --- a/tests/relativePositions.tests.js +++ b/tests/relativePositions.tests.js @@ -1,4 +1,3 @@ - import * as Y from '../src/index.js' import * as t from 'lib0/testing' @@ -86,6 +85,26 @@ export const testRelativePositionCase6 = tc => { checkRelativePositions(ytext) } +/** + * Testing https://github.com/yjs/yjs/issues/657 + * + * @param {t.TestCase} tc + */ +export const testRelativePositionCase7 = tc => { + const docA = new Y.Doc() + const textA = docA.getText('text') + textA.insert(0, 'abcde') + // Create a relative position at index 2 in 'textA' + const relativePosition = Y.createRelativePositionFromTypeIndex(textA, 2) + // Verify that the absolutes positions on 'docA' are the same + const absolutePositionWithFollow = + Y.createAbsolutePositionFromRelativePosition(relativePosition, docA, true) + const absolutePositionWithoutFollow = + Y.createAbsolutePositionFromRelativePosition(relativePosition, docA, false) + t.assert(absolutePositionWithFollow?.index === 2) + t.assert(absolutePositionWithoutFollow?.index === 2) +} + /** * @param {t.TestCase} tc */ @@ -102,3 +121,25 @@ export const testRelativePositionAssociationDifference = tc => { t.assert(posRight != null && posRight.index === 2) t.assert(posLeft != null && posLeft.index === 1) } + +/** + * @param {t.TestCase} tc + */ +export const testRelativePositionWithUndo = tc => { + const ydoc = new Y.Doc() + const ytext = ydoc.getText() + ytext.insert(0, 'hello world') + const rpos = Y.createRelativePositionFromTypeIndex(ytext, 1) + const um = new Y.UndoManager(ytext) + ytext.delete(0, 6) + t.assert(Y.createAbsolutePositionFromRelativePosition(rpos, ydoc)?.index === 0) + um.undo() + t.assert(Y.createAbsolutePositionFromRelativePosition(rpos, ydoc)?.index === 1) + const posWithoutFollow = Y.createAbsolutePositionFromRelativePosition(rpos, ydoc, false) + console.log({ posWithoutFollow }) + t.assert(Y.createAbsolutePositionFromRelativePosition(rpos, ydoc, false)?.index === 6) + const ydocClone = new Y.Doc() + Y.applyUpdate(ydocClone, Y.encodeStateAsUpdate(ydoc)) + t.assert(Y.createAbsolutePositionFromRelativePosition(rpos, ydocClone)?.index === 6) + t.assert(Y.createAbsolutePositionFromRelativePosition(rpos, ydocClone, false)?.index === 6) +} diff --git a/tests/snapshot.tests.js b/tests/snapshot.tests.js index 7f26718df..01c956f4f 100644 --- a/tests/snapshot.tests.js +++ b/tests/snapshot.tests.js @@ -3,9 +3,36 @@ import * as t from 'lib0/testing' import { init } from './testHelper.js' /** - * @param {t.TestCase} tc + * @param {t.TestCase} _tc + */ +export const testBasic = _tc => { + const ydoc = new Y.Doc({ gc: false }) + ydoc.getText().insert(0, 'world!') + const snapshot = Y.snapshot(ydoc) + ydoc.getText().insert(0, 'hello ') + const restored = Y.createDocFromSnapshot(ydoc, snapshot) + t.assert(restored.getText().toString() === 'world!') +} + +/** + * @param {t.TestCase} _tc */ -export const testBasicRestoreSnapshot = tc => { +export const testBasicXmlAttributes = _tc => { + const ydoc = new Y.Doc({ gc: false }) + const yxml = ydoc.getMap().set('el', new Y.XmlElement('div')) + const snapshot1 = Y.snapshot(ydoc) + yxml.setAttribute('a', '1') + const snapshot2 = Y.snapshot(ydoc) + yxml.setAttribute('a', '2') + t.compare(yxml.getAttributes(), { a: '2' }) + t.compare(yxml.getAttributes(snapshot2), { a: '1' }) + t.compare(yxml.getAttributes(snapshot1), {}) +} + +/** + * @param {t.TestCase} _tc + */ +export const testBasicRestoreSnapshot = _tc => { const doc = new Y.Doc({ gc: false }) doc.getArray('array').insert(0, ['hello']) const snap = Y.snapshot(doc) @@ -18,9 +45,9 @@ export const testBasicRestoreSnapshot = tc => { } /** - * @param {t.TestCase} tc + * @param {t.TestCase} _tc */ -export const testEmptyRestoreSnapshot = tc => { +export const testEmptyRestoreSnapshot = _tc => { const doc = new Y.Doc({ gc: false }) const snap = Y.snapshot(doc) snap.sv.set(9999, 0) @@ -31,16 +58,16 @@ export const testEmptyRestoreSnapshot = tc => { t.compare(docRestored.getArray().toArray(), []) t.compare(doc.getArray().toArray(), ['world']) - // now this snapshot reflects the latest state. It shoult still work. + // now this snapshot reflects the latest state. It should still work. const snap2 = Y.snapshot(doc) const docRestored2 = Y.createDocFromSnapshot(doc, snap2) t.compare(docRestored2.getArray().toArray(), ['world']) } /** - * @param {t.TestCase} tc + * @param {t.TestCase} _tc */ -export const testRestoreSnapshotWithSubType = tc => { +export const testRestoreSnapshotWithSubType = _tc => { const doc = new Y.Doc({ gc: false }) doc.getArray('array').insert(0, [new Y.Map()]) const subMap = doc.getArray('array').get(0) @@ -61,9 +88,9 @@ export const testRestoreSnapshotWithSubType = tc => { } /** - * @param {t.TestCase} tc + * @param {t.TestCase} _tc */ -export const testRestoreDeletedItem1 = tc => { +export const testRestoreDeletedItem1 = _tc => { const doc = new Y.Doc({ gc: false }) doc.getArray('array').insert(0, ['item1', 'item2']) @@ -77,9 +104,9 @@ export const testRestoreDeletedItem1 = tc => { } /** - * @param {t.TestCase} tc + * @param {t.TestCase} _tc */ -export const testRestoreLeftItem = tc => { +export const testRestoreLeftItem = _tc => { const doc = new Y.Doc({ gc: false }) doc.getArray('array').insert(0, ['item1']) doc.getMap('map').set('test', 1) @@ -95,9 +122,9 @@ export const testRestoreLeftItem = tc => { } /** - * @param {t.TestCase} tc + * @param {t.TestCase} _tc */ -export const testDeletedItemsBase = tc => { +export const testDeletedItemsBase = _tc => { const doc = new Y.Doc({ gc: false }) doc.getArray('array').insert(0, ['item1']) doc.getArray('array').delete(0) @@ -111,9 +138,9 @@ export const testDeletedItemsBase = tc => { } /** - * @param {t.TestCase} tc + * @param {t.TestCase} _tc */ -export const testDeletedItems2 = tc => { +export const testDeletedItems2 = _tc => { const doc = new Y.Doc({ gc: false }) doc.getArray('array').insert(0, ['item1', 'item2', 'item3']) doc.getArray('array').delete(1) @@ -169,3 +196,28 @@ export const testDependentChanges = tc => { const docRestored1 = Y.createDocFromSnapshot(array1.doc, snap) t.compare(docRestored1.getArray('array').toArray(), ['user1item1', 'user2item1']) } + +/** + * @param {t.TestCase} _tc + */ +export const testContainsUpdate = _tc => { + const ydoc = new Y.Doc() + /** + * @type {Array} + */ + const updates = [] + ydoc.on('update', update => { + updates.push(update) + }) + const yarr = ydoc.getArray() + const snapshot1 = Y.snapshot(ydoc) + yarr.insert(0, [1]) + const snapshot2 = Y.snapshot(ydoc) + yarr.delete(0, 1) + const snapshotFinal = Y.snapshot(ydoc) + t.assert(!Y.snapshotContainsUpdate(snapshot1, updates[0])) + t.assert(!Y.snapshotContainsUpdate(snapshot2, updates[1])) + t.assert(Y.snapshotContainsUpdate(snapshot2, updates[0])) + t.assert(Y.snapshotContainsUpdate(snapshotFinal, updates[0])) + t.assert(Y.snapshotContainsUpdate(snapshotFinal, updates[1])) +} diff --git a/tests/testHelper.js b/tests/testHelper.js index f571880ed..02fc7bc82 100644 --- a/tests/testHelper.js +++ b/tests/testHelper.js @@ -1,12 +1,18 @@ - import * as t from 'lib0/testing' import * as prng from 'lib0/prng' import * as encoding from 'lib0/encoding' import * as decoding from 'lib0/decoding' -import * as syncProtocol from 'y-protocols/sync' +import * as syncProtocol from '@y/protocols/sync' import * as object from 'lib0/object' import * as map from 'lib0/map' import * as Y from '../src/index.js' +import * as math from 'lib0/math' +import * as list from 'lib0/list' +import * as delta from 'lib0/delta' +import { + createIdSet, createIdMap, addToIdSet, encodeIdMap +} from '../src/internals.js' + export * from '../src/index.js' if (typeof window !== 'undefined') { @@ -35,7 +41,7 @@ export const encV1 = { mergeUpdates: Y.mergeUpdates, applyUpdate: Y.applyUpdate, logUpdate: Y.logUpdate, - updateEventName: 'update', + updateEventName: /** @type {'update'} */ ('update'), diffUpdate: Y.diffUpdate } @@ -44,7 +50,7 @@ export const encV2 = { mergeUpdates: Y.mergeUpdatesV2, applyUpdate: Y.applyUpdateV2, logUpdate: Y.logUpdateV2, - updateEventName: 'updateV2', + updateEventName: /** @type {'updateV2'} */ ('updateV2'), diffUpdate: Y.diffUpdateV2 } @@ -134,7 +140,7 @@ export class TestYInstance extends Y.Doc { * @param {TestYInstance} remoteClient */ _receive (message, remoteClient) { - map.setIfUndefined(this.receiving, remoteClient, () => []).push(message) + map.setIfUndefined(this.receiving, remoteClient, () => /** @type {Array} */ ([])).push(message) } } @@ -228,7 +234,7 @@ export class TestConnector { } /** - * @return {boolean} Whether it was possible to disconnect a randon connection. + * @return {boolean} Whether it was possible to disconnect a random connection. */ disconnectRandom () { if (this.onlineConns.size === 0) { @@ -298,6 +304,140 @@ export const init = (tc, { users = 5 } = {}, initTestObject) => { return /** @type {any} */ (result) } +/** + * @param {Y.IdSet} idSet1 + * @param {Y.IdSet} idSet2 + */ +export const compareIdSets = (idSet1, idSet2) => { + t.assert(idSet1.clients.size === idSet2.clients.size) + for (const [client, _items1] of idSet1.clients.entries()) { + const items1 = _items1.getIds() + const items2 = idSet2.clients.get(client)?.getIds() + t.assert(items2 !== undefined && items1.length === items2.length) + for (let i = 0; i < items1.length; i++) { + const di1 = items1[i] + const di2 = /** @type {Array} */ (items2)[i] + t.assert(di1.clock === di2.clock && di1.len === di2.len) + } + } + return true +} + +/** + * only use for testing + * + * @template T + * @param {Array>} attrs + * @param {Y.Attribution} attr + * + */ +const _idmapAttrsHas = (attrs, attr) => { + const hash = attr.hash() + return attrs.find(a => a.hash() === hash) +} + +/** + * only use for testing + * + * @template T + * @param {Array>} a + * @param {Array>} b + */ +export const _idmapAttrsEqual = (a, b) => a.length === b.length && a.every(v => _idmapAttrsHas(b, v)) + +/** + * Ensure that all attributes exist. Also create a copy and compare it to the original. + * + * @template T + * @param {Y.IdMap} idmap + */ +export const validateIdMap = idmap => { + const copy = Y.createIdMap() + idmap.clients.forEach((ranges, client) => { + ranges.getIds().forEach(range => { + range.attrs.forEach(attr => { + t.assert(idmap.attrs.has(attr)) + t.assert(idmap.attrsH.get(attr.hash()) === attr) + copy.add(client, range.clock, range.len, range.attrs.slice()) + }) + }) + t.assert(copy.clients.get(client)?.getIds().length === ranges.getIds().length) + }) + t.assert(idmap.attrsH.size === idmap.attrs.size) +} + +/** + * @template T + * @param {Y.IdMap} idmap1 + * @param {Y.IdMap} idmap2 + */ +export const compareIdmaps = (idmap1, idmap2) => { + t.assert(idmap1.clients.size === idmap2.clients.size) + for (const [client, _items1] of idmap1.clients.entries()) { + const items1 = _items1.getIds() + const items2 = idmap2.clients.get(client)?.getIds() + t.assert(items2 !== undefined && items1.length === items2.length) + for (let i = 0; i < items1.length; i++) { + const di1 = items1[i] + const di2 = /** @type {Array>} */ (items2)[i] + t.assert(di1.clock === di2.clock && di1.len === di2.len && _idmapAttrsEqual(di1.attrs, di2.attrs)) + } + } + validateIdMap(idmap1) + validateIdMap(idmap2) +} + +/** + * @param {prng.PRNG} gen + * @param {number} clients + * @param {number} clockRange (max clock - exclusive - by each client) + */ +export const createRandomIdSet = (gen, clients, clockRange) => { + const maxOpLen = 5 + const numOfOps = math.ceil((clients * clockRange) / maxOpLen) + const idset = createIdSet() + for (let i = 0; i < numOfOps; i++) { + const client = prng.uint32(gen, 0, clients - 1) + const clockStart = prng.uint32(gen, 0, clockRange) + const len = prng.uint32(gen, 0, clockRange - clockStart) + addToIdSet(idset, client, clockStart, len) + } + if (idset.clients.size === clients && clients > 1 && prng.bool(gen)) { + idset.clients.delete(prng.uint32(gen, 0, clients)) + } + return idset +} + +/** + * @template T + * @param {prng.PRNG} gen + * @param {number} clients + * @param {number} clockRange (max clock - exclusive - by each client) + * @param {Array} attrChoices (max clock - exclusive - by each client) + * @return {Y.IdMap} + */ +export const createRandomIdMap = (gen, clients, clockRange, attrChoices) => { + const maxOpLen = 5 + const numOfOps = math.ceil((clients * clockRange) / maxOpLen) + const idMap = createIdMap() + for (let i = 0; i < numOfOps; i++) { + const client = prng.uint32(gen, 0, clients - 1) + const clockStart = prng.uint32(gen, 0, clockRange) + const len = prng.uint32(gen, 0, clockRange - clockStart) + const attrs = [prng.oneOf(gen, attrChoices)] + // maybe add another attr + if (prng.bool(gen)) { + const a = prng.oneOf(gen, attrChoices) + if (attrs.find(attr => attr === a) == null) { + attrs.push(a) + } + } + idMap.add(client, clockStart, len, attrs.map(v => Y.createAttributionItem('', v))) + } + t.info(`Created IdMap with ${numOfOps} ranges and ${attrChoices.length} different attributes. Encoded size: ${encodeIdMap(idMap).byteLength}`) + return idMap +} + /** * 1. reconnect and flush all * 2. user 0 gc @@ -320,8 +460,8 @@ export const compare = users => { users.push(.../** @type {any} */(mergedDocs)) const userArrayValues = users.map(u => u.getArray('array').toJSON()) const userMapValues = users.map(u => u.getMap('map').toJSON()) - const userXmlValues = users.map(u => u.get('xml', Y.XmlElement).toString()) - const userTextValues = users.map(u => u.getText('text').toDelta()) + const userXmlValues = users.map(u => /** @type {Y.XmlElement} */ (u.get('xml', Y.XmlElement)).toString()) + const userTextValues = users.map(u => u.getText('text').getContentDeep()) for (const u of users) { t.assert(u.store.pendingDs === null) t.assert(u.store.pendingStructs === null) @@ -346,8 +486,8 @@ export const compare = users => { t.compare(userArrayValues[i], userArrayValues[i + 1]) t.compare(userMapValues[i], userMapValues[i + 1]) t.compare(userXmlValues[i], userXmlValues[i + 1]) - t.compare(userTextValues[i].map(/** @param {any} a */ a => typeof a.insert === 'string' ? a.insert : ' ').join('').length, users[i].getText('text').length) - t.compare(userTextValues[i], userTextValues[i + 1], '', (constructor, a, b) => { + t.compare(list.toArray(userTextValues[i].children).map(a => (delta.$textOp.check(a) || delta.$insertOp.check(a)) ? a.insert.length : 0).reduce((a, b) => a + b, 0), users[i].getText('text').length) + t.compare(userTextValues[i], userTextValues[i + 1], '', (_constructor, a, b) => { if (a instanceof Y.AbstractType) { t.compare(a.toJSON(), b.toJSON()) } else if (a !== b) { @@ -356,9 +496,13 @@ export const compare = users => { return true }) t.compare(Y.encodeStateVector(users[i]), Y.encodeStateVector(users[i + 1])) - compareDS(Y.createDeleteSetFromStructStore(users[i].store), Y.createDeleteSetFromStructStore(users[i + 1].store)) + Y.equalIdSets(Y.createDeleteSetFromStructStore(users[i].store), Y.createDeleteSetFromStructStore(users[i + 1].store)) compareStructStores(users[i].store, users[i + 1].store) + t.compare(Y.encodeSnapshot(Y.snapshot(users[i])), Y.encodeSnapshot(Y.snapshot(users[i + 1]))) } + users.forEach(user => { + compareIdSets(user.store.ds, Y.createDeleteSetFromStructStore(user.store)) + }) users.map(u => u.destroy()) } @@ -370,8 +514,8 @@ export const compare = users => { export const compareItemIDs = (a, b) => a === b || (a !== null && b != null && Y.compareIDs(a.id, b.id)) /** - * @param {import('../src/internals').StructStore} ss1 - * @param {import('../src/internals').StructStore} ss2 + * @param {import('../src/internals.js').StructStore} ss1 + * @param {import('../src/internals.js').StructStore} ss2 */ export const compareStructStores = (ss1, ss2) => { t.assert(ss1.clients.size === ss2.clients.size) @@ -412,25 +556,6 @@ export const compareStructStores = (ss1, ss2) => { } } -/** - * @param {import('../src/internals').DeleteSet} ds1 - * @param {import('../src/internals').DeleteSet} ds2 - */ -export const compareDS = (ds1, ds2) => { - t.assert(ds1.clients.size === ds2.clients.size) - ds1.clients.forEach((deleteItems1, client) => { - const deleteItems2 = /** @type {Array} */ (ds2.clients.get(client)) - t.assert(deleteItems2 !== undefined && deleteItems1.length === deleteItems2.length) - for (let i = 0; i < deleteItems1.length; i++) { - const di1 = deleteItems1[i] - const di2 = deleteItems2[i] - if (di1.clock !== di2.clock || di1.len !== di2.len) { - t.fail('DeleteSets dont match') - } - } - }) -} - /** * @template T * @callback InitTestObjectCallback diff --git a/tests/undo-redo.tests.js b/tests/undo-redo.tests.js index 3b3794f0c..c40c6985b 100644 --- a/tests/undo-redo.tests.js +++ b/tests/undo-redo.tests.js @@ -1,7 +1,50 @@ -import { init, compare, applyRandomTests, Doc } from './testHelper.js' // eslint-disable-line - import * as Y from '../src/index.js' +import { init } from './testHelper.js' // eslint-disable-line import * as t from 'lib0/testing' +import * as delta from 'lib0/delta' + +export const testInconsistentFormat = () => { + /** + * @param {Y.Doc} ydoc + */ + const testYjsMerge = ydoc => { + const content = /** @type {Y.XmlText} */ (ydoc.get('text', Y.XmlText)) + content.format(0, 6, { bold: null }) + content.format(6, 4, { type: 'text' }) + t.compare(content.getContent(), delta.create().insert('Merge Test', { type: 'text' }).insert(' After', { type: 'text', italic: true }).done()) + } + const initializeYDoc = () => { + const yDoc = new Y.Doc({ gc: false }) + const content = /** @type {Y.XmlText} */ (yDoc.get('text', Y.XmlText)) + content.insert(0, ' After', { type: 'text', italic: true }) + content.insert(0, 'Test', { type: 'text' }) + content.insert(0, 'Merge ', { type: 'text', bold: true }) + return yDoc + } + { + const yDoc = initializeYDoc() + testYjsMerge(yDoc) + } + { + const initialYDoc = initializeYDoc() + const yDoc = new Y.Doc({ gc: false }) + Y.applyUpdate(yDoc, Y.encodeStateAsUpdate(initialYDoc)) + testYjsMerge(yDoc) + } +} + +/** + * @param {t.TestCase} tc + */ +export const testInfiniteCaptureTimeout = tc => { + const { array0 } = init(tc, { users: 3 }) + const undoManager = new Y.UndoManager(array0, { captureTimeout: Number.MAX_VALUE }) + array0.push([1, 2, 3]) + undoManager.stopCapturing() + array0.push([4, 5, 6]) + undoManager.undo() + t.compare(array0.toArray(), [1, 2, 3]) +} /** * @param {t.TestCase} tc @@ -42,18 +85,18 @@ export const testUndoText = tc => { t.assert(text0.toString() === 'bcxyz') // test marks text0.format(1, 3, { bold: true }) - t.compare(text0.toDelta(), [{ insert: 'b' }, { insert: 'cxy', attributes: { bold: true } }, { insert: 'z' }]) + t.compare(text0.getContent(), delta.create().insert('b').insert('cxy', { bold: true }).insert('z')) undoManager.undo() - t.compare(text0.toDelta(), [{ insert: 'bcxyz' }]) + t.compare(text0.getContent(), delta.create().insert('bcxyz')) undoManager.redo() - t.compare(text0.toDelta(), [{ insert: 'b' }, { insert: 'cxy', attributes: { bold: true } }, { insert: 'z' }]) + t.compare(text0.getContent(), delta.create().insert('b').insert('cxy', { bold: true }).insert('z')) } /** * Test case to fix #241 - * @param {t.TestCase} tc + * @param {t.TestCase} _tc */ -export const testEmptyTypeScope = tc => { +export const testEmptyTypeScope = _tc => { const ydoc = new Y.Doc() const um = new Y.UndoManager([], { doc: ydoc }) const yarray = ydoc.getArray() @@ -63,11 +106,77 @@ export const testEmptyTypeScope = tc => { t.assert(yarray.length === 0) } +/** + * @param {t.TestCase} _tc + */ +export const testRejectUpdateExample = _tc => { + const tmpydoc1 = new Y.Doc() + tmpydoc1.getArray('restricted').insert(0, [1]) + tmpydoc1.getArray('public').insert(0, [1]) + const update1 = Y.encodeStateAsUpdate(tmpydoc1) + const tmpydoc2 = new Y.Doc() + tmpydoc2.getArray('public').insert(0, [2]) + const update2 = Y.encodeStateAsUpdate(tmpydoc2) + + const ydoc = new Y.Doc() + const restrictedType = ydoc.getArray('restricted') + + /** + * Assume this function handles incoming updates via a communication channel like websockets. + * Changes to the `ydoc.getMap('restricted')` type should be rejected. + * + * - set up undo manager on the restricted types + * - cache pending* updates from the Ydoc to avoid certain attacks + * - apply received update and check whether the restricted type (or any of its children) has been changed. + * - catch errors that might try to circumvent the restrictions + * - undo changes on restricted types + * - reapply pending* updates + * + * @param {Uint8Array} update + */ + const updateHandler = (update) => { + // don't handle changes of the local undo manager, which is used to undo invalid changes + const um = new Y.UndoManager(restrictedType, { trackedOrigins: new Set(['remote change']) }) + const beforePendingDs = ydoc.store.pendingDs + const beforePendingStructs = ydoc.store.pendingStructs?.update + try { + Y.applyUpdate(ydoc, update, 'remote change') + } finally { + while (um.undoStack.length) { + um.undo() + } + um.destroy() + ydoc.store.pendingDs = beforePendingDs + ydoc.store.pendingStructs = null + if (beforePendingStructs) { + Y.applyUpdateV2(ydoc, beforePendingStructs) + } + } + } + updateHandler(update1) + updateHandler(update2) + t.assert(restrictedType.length === 0) + t.assert(ydoc.getArray('public').length === 2) +} + /** * Test case to fix #241 - * @param {t.TestCase} tc + * @param {t.TestCase} _tc + */ +export const testGlobalScope = _tc => { + const ydoc = new Y.Doc() + const um = new Y.UndoManager(ydoc) + const yarray = ydoc.getArray() + yarray.insert(0, [1]) + um.undo() + t.assert(yarray.length === 0) +} + +/** + * Test case to fix #241 + * @param {t.TestCase} _tc */ -export const testDoubleUndo = tc => { +export const testDoubleUndo = _tc => { const doc = new Y.Doc() const text = doc.getText() text.insert(0, '1221') @@ -189,16 +298,18 @@ export const testUndoXml = tc => { t.assert(xml0.toString() === '

content

') // format textchild and revert that change undoManager.stopCapturing() - textchild.format(3, 4, { bold: {} }) - t.assert(xml0.toString() === '

content

') + textchild.format(3, 4, { bold: true }) + const v1 = delta.create('UNDEFINED').insert([delta.create('p').insert([delta.text().insert('con').insert('tent', { bold: true }).done()]).done()]).done() + const v2 = delta.create('UNDEFINED').insert([delta.create('p').insert([delta.text().insert('content').done()]).done()]).done() + t.compare(xml0.getContentDeep(), v1) undoManager.undo() - t.assert(xml0.toString() === '

content

') + t.compare(xml0.getContentDeep(), v2) undoManager.redo() - t.assert(xml0.toString() === '

content

') + t.compare(xml0.getContentDeep(), v1) xml0.delete(0, 1) - t.assert(xml0.toString() === '') + t.compare(xml0.getContentDeep(), delta.create('UNDEFINED')) undoManager.undo() - t.assert(xml0.toString() === '

content

') + t.compare(xml0.getContentDeep(), v1) } /** @@ -303,9 +414,9 @@ export const testUndoDeleteFilter = tc => { /** * This issue has been reported in https://discuss.yjs.dev/t/undomanager-with-external-updates/454/6 - * @param {t.TestCase} tc + * @param {t.TestCase} _tc */ -export const testUndoUntilChangePerformed = tc => { +export const testUndoUntilChangePerformed = _tc => { const doc = new Y.Doc() const doc2 = new Y.Doc() doc.on('update', update => Y.applyUpdate(doc2, update)) @@ -334,9 +445,9 @@ export const testUndoUntilChangePerformed = tc => { /** * This issue has been reported in https://github.com/yjs/yjs/issues/317 - * @param {t.TestCase} tc + * @param {t.TestCase} _tc */ -export const testUndoNestedUndoIssue = tc => { +export const testUndoNestedUndoIssue = _tc => { const doc = new Y.Doc({ gc: false }) const design = doc.getMap() const undoManager = new Y.UndoManager(design, { captureTimeout: 0 }) @@ -390,9 +501,9 @@ export const testUndoNestedUndoIssue = tc => { /** * This issue has been reported in https://github.com/yjs/yjs/issues/355 * - * @param {t.TestCase} tc + * @param {t.TestCase} _tc */ -export const testConsecutiveRedoBug = tc => { +export const testConsecutiveRedoBug = _tc => { const doc = new Y.Doc() const yRoot = doc.getMap() const undoMgr = new Y.UndoManager(yRoot) @@ -441,9 +552,9 @@ export const testConsecutiveRedoBug = tc => { /** * This issue has been reported in https://github.com/yjs/yjs/issues/304 * - * @param {t.TestCase} tc + * @param {t.TestCase} _tc */ -export const testUndoXmlBug = tc => { +export const testUndoXmlBug = _tc => { const origin = 'origin' const doc = new Y.Doc() const fragment = doc.getXmlFragment('t') @@ -486,9 +597,9 @@ export const testUndoXmlBug = tc => { /** * This issue has been reported in https://github.com/yjs/yjs/issues/343 * - * @param {t.TestCase} tc + * @param {t.TestCase} _tc */ -export const testUndoBlockBug = tc => { +export const testUndoBlockBug = _tc => { const doc = new Y.Doc({ gc: false }) const design = doc.getMap() @@ -546,9 +657,9 @@ export const testUndoBlockBug = tc => { * Undo text formatting delete should not corrupt peer state. * * @see https://github.com/yjs/yjs/issues/392 - * @param {t.TestCase} tc + * @param {t.TestCase} _tc */ -export const testUndoDeleteTextFormat = tc => { +export const testUndoDeleteTextFormat = _tc => { const doc = new Y.Doc() const text = doc.getText() text.insert(0, 'Attack ships on fire off the shoulder of Orion.') @@ -568,25 +679,21 @@ export const testUndoDeleteTextFormat = tc => { undoManager.undo() Y.applyUpdate(doc2, Y.encodeStateAsUpdate(doc)) - const expect = [ - { insert: 'Attack ships ' }, - { - insert: 'on fire', - attributes: { bold: true } - }, - { insert: ' off the shoulder of Orion.' } - ] - t.compare(text.toDelta(), expect) - t.compare(text2.toDelta(), expect) + const expect = delta.create() + .insert('Attack ships ') + .insert('on fire', { bold: true }) + .insert(' off the shoulder of Orion.') + t.compare(text.getContent(), expect) + t.compare(text2.getContent(), expect) } /** * Undo text formatting delete should not corrupt peer state. * * @see https://github.com/yjs/yjs/issues/392 - * @param {t.TestCase} tc + * @param {t.TestCase} _tc */ -export const testBehaviorOfIgnoreremotemapchangesProperty = tc => { +export const testBehaviorOfIgnoreremotemapchangesProperty = _tc => { const doc = new Y.Doc() const doc2 = new Y.Doc() doc.on('update', update => Y.applyUpdate(doc2, update, doc)) @@ -607,9 +714,9 @@ export const testBehaviorOfIgnoreremotemapchangesProperty = tc => { * Special deletion case. * * @see https://github.com/yjs/yjs/issues/447 - * @param {t.TestCase} tc + * @param {t.TestCase} _tc */ -export const testSpecialDeletionCase = tc => { +export const testSpecialDeletionCase = _tc => { const origin = 'undoable' const doc = new Y.Doc() const fragment = doc.getXmlFragment() @@ -631,3 +738,64 @@ export const testSpecialDeletionCase = tc => { undoManager.undo() t.compareStrings(fragment.toString(), '') } + +/** + * Deleted entries in a map should be restored on undo. + * + * @see https://github.com/yjs/yjs/issues/500 + * @param {t.TestCase} tc + */ +export const testUndoDeleteInMap = (tc) => { + const { map0 } = init(tc, { users: 3 }) + const undoManager = new Y.UndoManager(map0, { captureTimeout: 0 }) + map0.set('a', 'a') + map0.delete('a') + map0.set('a', 'b') + map0.delete('a') + map0.set('a', 'c') + map0.delete('a') + map0.set('a', 'd') + t.compare(map0.toJSON(), { a: 'd' }) + undoManager.undo() + t.compare(map0.toJSON(), {}) + undoManager.undo() + t.compare(map0.toJSON(), { a: 'c' }) + undoManager.undo() + t.compare(map0.toJSON(), {}) + undoManager.undo() + t.compare(map0.toJSON(), { a: 'b' }) + undoManager.undo() + t.compare(map0.toJSON(), {}) + undoManager.undo() + t.compare(map0.toJSON(), { a: 'a' }) +} + +/** + * It should expose the StackItem being processed if undoing + * + * @param {t.TestCase} _tc + */ +export const testUndoDoingStackItem = async (_tc) => { + const doc = new Y.Doc() + const text = doc.getText('text') + const undoManager = new Y.UndoManager([text]) + undoManager.on('stack-item-added', /** @param {any} event */ event => { + event.stackItem.meta.set('str', '42') + }) + let metaUndo = /** @type {any} */ (null) + let metaRedo = /** @type {any} */ (null) + text.observe((event) => { + const /** @type {Y.UndoManager} */ origin = event.transaction.origin + if (origin === undoManager && origin.undoing) { + metaUndo = origin.currStackItem?.meta.get('str') + } else if (origin === undoManager && origin.redoing) { + metaRedo = origin.currStackItem?.meta.get('str') + } + }) + text.insert(0, 'abc') + undoManager.undo() + undoManager.redo() + t.compare(metaUndo, '42', 'currStackItem is accessible while undoing') + t.compare(metaRedo, '42', 'currStackItem is accessible while redoing') + t.compare(undoManager.currStackItem, null, 'currStackItem is null after observe/transaction') +} diff --git a/tests/updates.tests.js b/tests/updates.tests.js index dce09e71b..8081007c6 100644 --- a/tests/updates.tests.js +++ b/tests/updates.tests.js @@ -1,9 +1,11 @@ import * as t from 'lib0/testing' -import { init, compare } from './testHelper.js' // eslint-disable-line import * as Y from '../src/index.js' -import { readClientsStructRefs, readDeleteSet, UpdateDecoderV2, UpdateEncoderV2, writeDeleteSet } from '../src/internals.js' +import { init, compare } from './testHelper.js' // eslint-disable-line +import { readStructSet, readIdSet, UpdateDecoderV2, UpdateEncoderV2, writeIdSet } from '../src/internals.js' import * as encoding from 'lib0/encoding' import * as decoding from 'lib0/decoding' +import * as object from 'lib0/object' +import * as delta from 'lib0/delta' /** * @typedef {Object} Enc @@ -14,7 +16,7 @@ import * as decoding from 'lib0/decoding' * @property {function(Uint8Array):{from:Map,to:Map}} Enc.parseUpdateMeta * @property {function(Y.Doc):Uint8Array} Enc.encodeStateVector * @property {function(Uint8Array):Uint8Array} Enc.encodeStateVectorFromUpdate - * @property {string} Enc.updateEventName + * @property {'update'|'updateV2'} Enc.updateEventName * @property {string} Enc.description * @property {function(Uint8Array, Uint8Array):Uint8Array} Enc.diffUpdate */ @@ -125,7 +127,15 @@ export const testKeyEncoding = tc => { const update = Y.encodeStateAsUpdateV2(users[0]) Y.applyUpdateV2(users[1], update) - t.compare(text1.toDelta(), [{ insert: 'c', attributes: { italic: true } }, { insert: 'b' }, { insert: 'a', attributes: { italic: true } }]) + const c = text1.getContent() + t.compare( + c, + delta.create() + .insert('c', { italic: true }) + .insert('b') + .insert('a', { italic: true }) + .done() + ) compare(users) } @@ -138,7 +148,6 @@ export const testKeyEncoding = tc => { */ const checkUpdateCases = (ydoc, updates, enc, hasDeletes) => { const cases = [] - // Case 1: Simple case, simply merge everything cases.push(enc.mergeUpdates(updates)) @@ -170,18 +179,17 @@ const checkUpdateCases = (ydoc, updates, enc, hasDeletes) => { // enc.logUpdate(targetState) cases.forEach((mergedUpdates, i) => { - // t.info('State Case $' + i + ':') + t.info(`State Case $${i} (${enc.description}):`) // enc.logUpdate(updates) const merged = new Y.Doc({ gc: false }) enc.applyUpdate(merged, mergedUpdates) t.compareArrays(merged.getArray().toArray(), ydoc.getArray().toArray()) t.compare(enc.encodeStateVector(merged), enc.encodeStateVectorFromUpdate(mergedUpdates)) - if (enc.updateEventName !== 'update') { // @todo should this also work on legacy updates? for (let j = 1; j < updates.length; j++) { const partMerged = enc.mergeUpdates(updates.slice(j)) const partMeta = enc.parseUpdateMeta(partMerged) - const targetSV = Y.encodeStateVectorFromUpdateV2(Y.mergeUpdatesV2(updates.slice(0, j))) + const targetSV = enc.encodeStateVectorFromUpdate(enc.mergeUpdates(updates.slice(0, j))) const diffed = enc.diffUpdate(mergedUpdates, targetSV) const diffedMeta = enc.parseUpdateMeta(diffed) t.compare(partMeta, diffedMeta) @@ -192,11 +200,11 @@ const checkUpdateCases = (ydoc, updates, enc, hasDeletes) => { // So we add all deletes from `diffed` to `partDeletes` and compare then const decoder = decoding.createDecoder(diffed) const updateDecoder = new UpdateDecoderV2(decoder) - readClientsStructRefs(updateDecoder, new Y.Doc()) - const ds = readDeleteSet(updateDecoder) + readStructSet(updateDecoder, new Y.Doc()) + const ds = readIdSet(updateDecoder) const updateEncoder = new UpdateEncoderV2() encoding.writeVarUint(updateEncoder.restEncoder, 0) // 0 structs - writeDeleteSet(updateEncoder, ds) + writeIdSet(updateEncoder, ds) const deletesUpdate = updateEncoder.toUint8Array() const mergedDeletes = Y.mergeUpdatesV2([deletesUpdate, partMerged]) if (!hasDeletes || enc !== encDoc) { @@ -208,7 +216,7 @@ const checkUpdateCases = (ydoc, updates, enc, hasDeletes) => { } const meta = enc.parseUpdateMeta(mergedUpdates) - meta.from.forEach((clock, client) => t.assert(clock === 0)) + meta.from.forEach((clock, _client) => t.assert(clock === 0)) meta.to.forEach((clock, client) => { const structs = /** @type {Array} */ (merged.store.clients.get(client)) const lastStruct = structs[structs.length - 1] @@ -218,10 +226,10 @@ const checkUpdateCases = (ydoc, updates, enc, hasDeletes) => { } /** - * @param {t.TestCase} tc + * @param {t.TestCase} _tc */ -export const testMergeUpdates1 = tc => { - encoders.forEach((enc, i) => { +export const testMergeUpdates1 = _tc => { + encoders.forEach((enc) => { t.info(`Using encoder: ${enc.description}`) const ydoc = new Y.Doc({ gc: false }) const updates = /** @type {Array} */ ([]) @@ -238,10 +246,10 @@ export const testMergeUpdates1 = tc => { } /** - * @param {t.TestCase} tc + * @param {t.TestCase} _tc */ -export const testMergeUpdates2 = tc => { - encoders.forEach((enc, i) => { +export const testMergeUpdates2 = _tc => { + encoders.forEach((enc, _i) => { t.info(`Using encoder: ${enc.description}`) const ydoc = new Y.Doc({ gc: false }) const updates = /** @type {Array} */ ([]) @@ -258,23 +266,23 @@ export const testMergeUpdates2 = tc => { } /** - * @param {t.TestCase} tc + * @param {t.TestCase} _tc */ -export const testMergePendingUpdates = tc => { +export const testMergePendingUpdates = _tc => { const yDoc = new Y.Doc() /** * @type {Array} */ const serverUpdates = [] - yDoc.on('update', (update, origin, c) => { + yDoc.on('update', (update, _origin, _c) => { serverUpdates.splice(serverUpdates.length, 0, update) }) const yText = yDoc.getText('textBlock') - yText.applyDelta([{ insert: 'r' }]) - yText.applyDelta([{ insert: 'o' }]) - yText.applyDelta([{ insert: 'n' }]) - yText.applyDelta([{ insert: 'e' }]) - yText.applyDelta([{ insert: 'n' }]) + yText.applyDelta(delta.create().insert('r')) + yText.applyDelta(delta.create().insert('o')) + yText.applyDelta(delta.create().insert('n')) + yText.applyDelta(delta.create().insert('e')) + yText.applyDelta(delta.create().insert('n')) const yDoc1 = new Y.Doc() Y.applyUpdate(yDoc1, serverUpdates[0]) @@ -298,9 +306,59 @@ export const testMergePendingUpdates = tc => { const yDoc5 = new Y.Doc() Y.applyUpdate(yDoc5, update4) Y.applyUpdate(yDoc5, serverUpdates[4]) - // @ts-ignore - const update5 = Y.encodeStateAsUpdate(yDoc5) // eslint-disable-line + Y.encodeStateAsUpdate(yDoc5) const yText5 = yDoc5.getText('textBlock') t.compareStrings(yText5.toString(), 'nenor') } + +/** + * @param {t.TestCase} _tc + */ +export const testObfuscateUpdates = _tc => { + const ydoc = new Y.Doc() + const ytext = ydoc.getText('text') + const ymap = ydoc.getMap('map') + const yarray = ydoc.getArray('array') + // test ytext + ytext.applyDelta(delta.create().insert('text', { bold: true }).insert([{ href: 'supersecreturl' }])) + // test ymap + ymap.set('key', 'secret1') + ymap.set('key', 'secret2') + // test yarray with subtype & subdoc + const subtype = new Y.XmlElement('secretnodename') + const subdoc = new Y.Doc({ guid: 'secret' }) + subtype.setAttribute('attr', 'val') + yarray.insert(0, ['teststring', 42, subtype, subdoc]) + // obfuscate the content and put it into a new document + const obfuscatedUpdate = Y.obfuscateUpdate(Y.encodeStateAsUpdate(ydoc)) + const odoc = new Y.Doc() + Y.applyUpdate(odoc, obfuscatedUpdate) + const otext = odoc.getText('text') + const omap = odoc.getMap('map') + const oarray = odoc.getArray('array') + // test ytext + const d = /** @type {any} */ (otext.getContent().toJSON().children) + t.assert(d.length === 2) + t.assert(d[0].insert !== 'text' && d[0].insert.length === 4) + t.assert(object.length(d[0].format) === 1) + t.assert(!object.hasProperty(d[0].format, 'bold')) + t.assert(object.length(d[1].insert) === 1) + t.assert(object.hasProperty(d[1], 'insert')) + // test ymap + t.assert(omap.size === 1) + t.assert(!omap.has('key')) + // test yarray with subtype & subdoc + const result = oarray.toArray() + t.assert(result.length === 4) + t.assert(result[0] !== 'teststring') + t.assert(result[1] !== 42) + const osubtype = /** @type {Y.XmlElement} */ (result[2]) + const osubdoc = result[3] + // test subtype + t.assert(osubtype.nodeName !== subtype.nodeName) + t.assert(object.length(osubtype.getAttributes()) === 1) + t.assert(osubtype.getAttribute('attr') === undefined) + // test subdoc + t.assert(osubdoc.guid !== subdoc.guid) +} diff --git a/tests/y-array.tests.js b/tests/y-array.tests.js index 8cdaac5b6..29ccf2927 100644 --- a/tests/y-array.tests.js +++ b/tests/y-array.tests.js @@ -1,14 +1,17 @@ import { init, compare, applyRandomTests, Doc } from './testHelper.js' // eslint-disable-line - import * as Y from '../src/index.js' import * as t from 'lib0/testing' import * as prng from 'lib0/prng' import * as math from 'lib0/math' +import * as env from 'lib0/environment' +import * as delta from 'lib0/delta' + +const isDevMode = env.getVariable('node_env') === 'development' /** - * @param {t.TestCase} tc + * @param {t.TestCase} _tc */ -export const testBasicUpdate = tc => { +export const testBasicUpdate = _tc => { const doc1 = new Y.Doc() const doc2 = new Y.Doc() doc1.getArray('array').insert(0, ['hi']) @@ -18,9 +21,31 @@ export const testBasicUpdate = tc => { } /** - * @param {t.TestCase} tc + * @param {t.TestCase} _tc + */ +export const testFailsObjectManipulationInDevMode = _tc => { + if (isDevMode) { + t.info('running in dev mode') + const doc = new Y.Doc() + const a = [1, 2, 3] + const b = { o: 1 } + doc.getArray('test').insert(0, [a]) + doc.getMap('map').set('k', b) + t.fails(() => { + a[0] = 42 + }) + t.fails(() => { + b.o = 42 + }) + } else { + t.info('not in dev mode') + } +} + +/** + * @param {t.TestCase} _tc */ -export const testSlice = tc => { +export const testSlice = _tc => { const doc1 = new Y.Doc() const arr = doc1.getArray('array') arr.insert(0, [1, 2, 3]) @@ -32,12 +57,23 @@ export const testSlice = tc => { t.compareArrays(arr.slice(0, 2), [0, 1]) } +/** + * @param {t.TestCase} _tc + */ +export const testArrayFrom = _tc => { + const doc1 = new Y.Doc() + const db1 = doc1.getMap('root') + const nestedArray1 = Y.Array.from([0, 1, 2]) + db1.set('array', nestedArray1) + t.compare(nestedArray1.toArray(), [0, 1, 2]) +} + /** * Debugging yjs#297 - a critical bug connected to the search-marker approach * - * @param {t.TestCase} tc + * @param {t.TestCase} _tc */ -export const testLengthIssue = tc => { +export const testLengthIssue = _tc => { const doc1 = new Y.Doc() const arr = doc1.getArray('array') arr.push([0, 1, 2, 3]) @@ -64,9 +100,9 @@ export const testLengthIssue = tc => { /** * Debugging yjs#314 * - * @param {t.TestCase} tc + * @param {t.TestCase} _tc */ -export const testLengthIssue2 = tc => { +export const testLengthIssue2 = _tc => { const doc = new Y.Doc() const next = doc.getArray() doc.transact(() => { @@ -253,7 +289,7 @@ export const testNestedObserverEvents = tc => { * @type {Array} */ const vals = [] - array0.observe(e => { + array0.observe(() => { if (array0.length === 1) { // inserting, will call this observer again // we expect that this observer is called after this event handler finishedn @@ -319,30 +355,51 @@ export const testObserveDeepEventOrder = tc => { } } +/** + * Correct index when computing event.path in observeDeep - https://github.com/yjs/yjs/issues/457 + * + * @param {t.TestCase} _tc + */ +export const testObservedeepIndexes = _tc => { + const doc = new Y.Doc() + const map = doc.getMap() + // Create a field with the array as value + map.set('my-array', new Y.Array()) + // Fill the array with some strings and our Map + map.get('my-array').push(['a', 'b', 'c', new Y.Map()]) + /** + * @type {Array} + */ + let eventPath = [] + map.observeDeep((events) => { eventPath = events[0].path }) + // set a value on the map inside of our array + map.get('my-array').get(3).set('hello', 'world') + console.log(eventPath) + t.compare(eventPath, ['my-array', 3]) +} + /** * @param {t.TestCase} tc */ export const testChangeEvent = tc => { const { array0, users } = init(tc, { users: 2 }) /** - * @type {any} + * @type {delta.Delta} */ - let changes = null + let d = delta.create() array0.observe(e => { - changes = e.changes + d = e.delta }) const newArr = new Y.Array() array0.insert(0, [newArr, 4, 'dtrn']) - t.assert(changes !== null && changes.added.size === 2 && changes.deleted.size === 0) - t.compare(changes.delta, [{ insert: [newArr, 4, 'dtrn'] }]) - changes = null + t.assert(d !== null && d.children.len === 1) + t.compare(d, delta.create().insert([newArr, 4, 'dtrn'])) array0.delete(0, 2) - t.assert(changes !== null && changes.added.size === 0 && changes.deleted.size === 2) - t.compare(changes.delta, [{ delete: 2 }]) - changes = null + t.assert(d !== null && d.children.len === 1) + t.compare(d.toJSON().children, [{ delete: 2 }]) array0.insert(1, [0.1]) - t.assert(changes !== null && changes.added.size === 1 && changes.deleted.size === 0) - t.compare(changes.delta, [{ retain: 1 }, { insert: [0.1] }]) + t.assert(d !== null && d.children.len === 2) + t.compare(d, delta.create().retain(1).insert([0.1]).done()) compare(users) } @@ -433,9 +490,9 @@ export const testEventTargetIsSetCorrectlyOnRemote = tc => { } /** - * @param {t.TestCase} tc + * @param {t.TestCase} _tc */ -export const testIteratingArrayContainingTypes = tc => { +export const testIteratingArrayContainingTypes = _tc => { const y = new Y.Doc() const arr = y.getArray('arr') const numItems = 10 @@ -451,6 +508,34 @@ export const testIteratingArrayContainingTypes = tc => { y.destroy() } +/** + * @param {t.TestCase} _tc + */ +export const testAttributedContent = _tc => { + const ydoc = new Y.Doc({ gc: false }) + /** + * @type {Y.Array} + */ + const yarray = ydoc.getArray() + yarray.insert(0, [1, 2]) + let attributionManager = Y.noAttributionsManager + + ydoc.on('afterTransaction', tr => { + // attributionManager = new TwosetAttributionManager(createIdMapFromIdSet(tr.insertSet, [new Y.Attribution('insertedAt', 42), new Y.Attribution('insert', 'kevin')]), createIdMapFromIdSet(tr.deleteSet, [new Y.Attribution('delete', 'kevin')])) + attributionManager = new Y.TwosetAttributionManager(Y.createIdMapFromIdSet(tr.insertSet, []), Y.createIdMapFromIdSet(tr.deleteSet, [])) + }) + t.group('insert / delete', () => { + ydoc.transact(() => { + yarray.delete(0, 1) + yarray.insert(1, [42]) + }) + const expectedContent = delta.create().insert([1], null, { delete: [] }).insert([2]).insert([42], null, { insert: [] }) + const attributedContent = yarray.getContent(attributionManager) + console.log(attributedContent.toJSON()) + t.assert(attributedContent.equals(expectedContent)) + }) +} + let _uniqueNumber = 0 const getUniqueNumber = () => _uniqueNumber++ diff --git a/tests/y-map.tests.js b/tests/y-map.tests.js index 83f31a48f..4fa5e9649 100644 --- a/tests/y-map.tests.js +++ b/tests/y-map.tests.js @@ -1,12 +1,64 @@ +import * as Y from '../src/index.js' import { init, compare, applyRandomTests, Doc } from './testHelper.js' // eslint-disable-line - import { - compareIDs + compareIDs, + noAttributionsManager, + TwosetAttributionManager, + createIdMapFromIdSet } from '../src/internals.js' - -import * as Y from '../src/index.js' import * as t from 'lib0/testing' import * as prng from 'lib0/prng' +import * as delta from 'lib0/delta' +import * as s from 'lib0/schema' +import * as object from 'lib0/object' + +/** + * @param {t.TestCase} _tc + */ +export const testIterators = _tc => { + const ydoc = new Y.Doc() + /** + * @type {Y.Map} + */ + const ymap = ydoc.getMap() + // we are only checking if the type assumptions are correct + /** + * @type {Array} + */ + const vals = Array.from(ymap.values()) + /** + * @type {Array<[string,number]>} + */ + const entries = Array.from(ymap.entries()) + /** + * @type {Array} + */ + const keys = Array.from(ymap.keys()) + console.log(vals, entries, keys) +} + +/** + * Computing event changes after transaction should result in an error. See yjs#539 + * + * @param {t.TestCase} _tc + */ +export const testMapEventError = _tc => { + const doc = new Y.Doc() + const ymap = doc.getMap() + /** + * @type {any} + */ + let event = null + ymap.observe((e) => { + event = e + }) + t.fails(() => { + t.info(event.keys) + }) + t.fails(() => { + t.info(event.keys) + }) +} /** * @param {t.TestCase} tc @@ -321,11 +373,11 @@ export const testObserversUsingObservedeep = tc => { /** * @type {Array>} */ - const pathes = [] + const paths = [] let calls = 0 map0.observeDeep(events => { events.forEach(event => { - pathes.push(event.path) + paths.push(event.path) }) calls++ }) @@ -333,7 +385,35 @@ export const testObserversUsingObservedeep = tc => { map0.get('map').set('array', new Y.Array()) map0.get('map').get('array').insert(0, ['content']) t.assert(calls === 3) - t.compare(pathes, [[], ['map'], ['map', 'array']]) + t.compare(paths, [[], ['map'], ['map', 'array']]) + compare(users) +} + +/** + * @param {t.TestCase} tc + */ +export const testPathsOfSiblingEvents = tc => { + const { users, map0 } = init(tc, { users: 2 }) + /** + * @type {Array>} + */ + const paths = [] + let calls = 0 + const doc = users[0] + map0.set('map', new Y.Map()) + map0.get('map').set('text1', new Y.Text('initial')) + map0.observeDeep(events => { + events.forEach(event => { + paths.push(event.path) + }) + calls++ + }) + doc.transact(() => { + map0.get('map').get('text1').insert(0, 'post-') + map0.get('map').set('text2', new Y.Text('new')) + }) + t.assert(calls === 1) + t.compare(paths, [['map'], ['map', 'text1']]) compare(users) } @@ -412,52 +492,48 @@ export const testThrowsDeleteEventsOnClear = tc => { export const testChangeEvent = tc => { const { map0, users } = init(tc, { users: 2 }) /** - * @type {any} - */ - let changes = null - /** - * @type {any} + * @type {delta.Delta?} */ - let keyChange = null + let changes = delta.create() map0.observe(e => { - changes = e.changes + changes = e.delta }) map0.set('a', 1) - keyChange = changes.keys.get('a') - t.assert(changes !== null && keyChange.action === 'add' && keyChange.oldValue === undefined) + let keyChange = changes.attrs.a + t.assert(delta.$insertOpWith(s.$number).check(keyChange) && keyChange.prevValue === undefined) map0.set('a', 2) - keyChange = changes.keys.get('a') - t.assert(changes !== null && keyChange.action === 'update' && keyChange.oldValue === 1) + keyChange = changes.attrs.a + t.assert(delta.$insertOpWith(s.$number).check(keyChange) && keyChange.prevValue === 1) users[0].transact(() => { map0.set('a', 3) map0.set('a', 4) }) - keyChange = changes.keys.get('a') - t.assert(changes !== null && keyChange.action === 'update' && keyChange.oldValue === 2) + keyChange = changes.attrs.a + t.assert(delta.$insertOpWith(s.$number).check(keyChange) && keyChange.prevValue === 2) users[0].transact(() => { map0.set('b', 1) map0.set('b', 2) }) - keyChange = changes.keys.get('b') - t.assert(changes !== null && keyChange.action === 'add' && keyChange.oldValue === undefined) + keyChange = changes.attrs.b + t.assert(delta.$insertOpWith(s.$number).check(keyChange) && keyChange.prevValue === undefined) users[0].transact(() => { map0.set('c', 1) map0.delete('c') }) - t.assert(changes !== null && changes.keys.size === 0) + t.assert(changes !== null && object.isEmpty(changes.attrs)) users[0].transact(() => { map0.set('d', 1) map0.set('d', 2) }) - keyChange = changes.keys.get('d') - t.assert(changes !== null && keyChange.action === 'add' && keyChange.oldValue === undefined) + keyChange = changes.attrs.d + t.assert(delta.$insertOpWith(s.$number).check(keyChange) && keyChange.prevValue === undefined) compare(users) } /** - * @param {t.TestCase} tc + * @param {t.TestCase} _tc */ -export const testYmapEventExceptionsShouldCompleteTransaction = tc => { +export const testYmapEventExceptionsShouldCompleteTransaction = _tc => { const doc = new Y.Doc() const map = doc.getMap('map') @@ -539,6 +615,41 @@ export const testYmapEventHasCorrectValueWhenSettingAPrimitiveFromOtherUser = tc compare(users) } +/** + * @param {t.TestCase} _tc + */ +export const testAttributedContent = _tc => { + const ydoc = new Y.Doc({ gc: false }) + const ymap = ydoc.getMap() + let attributionManager = noAttributionsManager + + ydoc.on('afterTransaction', tr => { + // attributionManager = new TwosetAttributionManager(createIdMapFromIdSet(tr.insertSet, [new Y.Attribution('insertedAt', 42), new Y.Attribution('insert', 'kevin')]), createIdMapFromIdSet(tr.deleteSet, [new Y.Attribution('delete', 'kevin')])) + attributionManager = new TwosetAttributionManager(createIdMapFromIdSet(tr.insertSet, []), createIdMapFromIdSet(tr.deleteSet, [])) + }) + t.group('initial value', () => { + ymap.set('test', 42) + const expectedContent = { test: delta.$deltaMapChangeJson.expect({ type: 'insert', value: 42, attribution: { insert: [] } }) } + const attributedContent = ymap.getContent(attributionManager) + console.log(attributedContent.toJSON()) + t.compare(expectedContent, attributedContent.toJSON().attrs) + }) + t.group('overwrite value', () => { + ymap.set('test', 'fourtytwo') + const expectedContent = { test: delta.$deltaMapChangeJson.expect({ type: 'insert', value: 'fourtytwo', attribution: { insert: [] } }) } + const attributedContent = ymap.getContent(attributionManager) + console.log(attributedContent) + t.compare(expectedContent, attributedContent.toJSON().attrs) + }) + t.group('delete value', () => { + ymap.delete('test') + const expectedContent = { test: delta.$deltaMapChangeJson.expect({ type: 'delete', prevValue: 'fourtytwo', attribution: { delete: [] } }) } + const attributedContent = ymap.getContent(attributionManager) + console.log(attributedContent.toJSON()) + t.compare(expectedContent, attributedContent.toJSON().attrs) + }) +} + /** * @type {Array} */ diff --git a/tests/y-text.tests.js b/tests/y-text.tests.js index 4f2fe090f..b84d67e8e 100644 --- a/tests/y-text.tests.js +++ b/tests/y-text.tests.js @@ -2,9 +2,1228 @@ import * as Y from './testHelper.js' import * as t from 'lib0/testing' import * as prng from 'lib0/prng' import * as math from 'lib0/math' +import * as delta from 'lib0/delta' +import * as list from 'lib0/list' +import { createIdMapFromIdSet, noAttributionsManager, TwosetAttributionManager, createAttributionManagerFromSnapshots } from 'yjs/internals' const { init, compare } = Y +/** + * https://github.com/yjs/yjs/issues/474 + * @todo Remove debug: 127.0.0.1:8080/test.html?filter=\[88/ + * @param {t.TestCase} _tc + */ +export const testDeltaBug = _tc => { + const initialDelta = delta.create() + .insert('\n', { + 'block-id': 'block-28eea923-9cbb-4b6f-a950-cf7fd82bc087' + }) + .insert('\n\n\n', { + 'table-col': { + width: '150' + } + }) + .insert('\n', { + 'block-id': 'block-9144be72-e528-4f91-b0b2-82d20408e9ea', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-6kv2ls', + cell: 'cell-apba4k' + }, + row: 'row-6kv2ls', + cell: 'cell-apba4k', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { + 'block-id': 'block-639adacb-1516-43ed-b272-937c55669a1c', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-6kv2ls', + cell: 'cell-a8qf0r' + }, + row: 'row-6kv2ls', + cell: 'cell-a8qf0r', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { + 'block-id': 'block-6302ca4a-73a3-4c25-8c1e-b542f048f1c6', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-6kv2ls', + cell: 'cell-oi9ikb' + }, + row: 'row-6kv2ls', + cell: 'cell-oi9ikb', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { + 'block-id': 'block-ceeddd05-330e-4f86-8017-4a3a060c4627', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-d1sv2g', + cell: 'cell-dt6ks2' + }, + row: 'row-d1sv2g', + cell: 'cell-dt6ks2', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { + 'block-id': 'block-37b19322-cb57-4e6f-8fad-0d1401cae53f', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-d1sv2g', + cell: 'cell-qah2ay' + }, + row: 'row-d1sv2g', + cell: 'cell-qah2ay', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { + 'block-id': 'block-468a69b5-9332-450b-9107-381d593de249', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-d1sv2g', + cell: 'cell-fpcz5a' + }, + row: 'row-d1sv2g', + cell: 'cell-fpcz5a', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { + 'block-id': 'block-26b1d252-9b2e-4808-9b29-04e76696aa3c', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-pflz90', + cell: 'cell-zrhylp' + }, + row: 'row-pflz90', + cell: 'cell-zrhylp', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { + 'block-id': 'block-6af97ba7-8cf9-497a-9365-7075b938837b', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-pflz90', + cell: 'cell-s1q9nt' + }, + row: 'row-pflz90', + cell: 'cell-s1q9nt', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { + 'block-id': 'block-107e273e-86bc-44fd-b0d7-41ab55aca484', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-pflz90', + cell: 'cell-20b0j9' + }, + row: 'row-pflz90', + cell: 'cell-20b0j9', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { + 'block-id': 'block-38161f9c-6f6d-44c5-b086-54cc6490f1e3' + }) + .insert('Content after table') + .insert('\n', { + 'block-id': 'block-15630542-ef45-412d-9415-88f0052238ce' + }) + const ydoc1 = new Y.Doc() + const ytext = ydoc1.getText() + ytext.applyDelta(initialDelta) + const addingDash = delta.create().retain(12).insert('-') + ytext.applyDelta(addingDash) + const addingSpace = delta.create().retain(13).insert(' ') + ytext.applyDelta(addingSpace) + const addingList = delta.create().retain(12).delete(2).retain(1, { + // Clear table line attribute + 'table-cell-line': null, + // Add list attribute in place of table-cell-line + list: { + rowspan: '1', + colspan: '1', + row: 'row-pflz90', + cell: 'cell-20b0j9', + list: 'bullet' + } + }) + ytext.applyDelta(addingList) + const result = ytext.getContent() + const expectedResult = delta.text() + .insert('\n', { 'block-id': 'block-28eea923-9cbb-4b6f-a950-cf7fd82bc087' }) + .insert('\n\n\n', { 'table-col': { width: '150' } }) + .insert('\n', { + 'block-id': 'block-9144be72-e528-4f91-b0b2-82d20408e9ea', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-6kv2ls', + cell: 'cell-apba4k' + }, + row: 'row-6kv2ls', + cell: 'cell-apba4k', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { + 'block-id': 'block-639adacb-1516-43ed-b272-937c55669a1c', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-6kv2ls', + cell: 'cell-a8qf0r' + }, + row: 'row-6kv2ls', + cell: 'cell-a8qf0r', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { + 'block-id': 'block-6302ca4a-73a3-4c25-8c1e-b542f048f1c6', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-6kv2ls', + cell: 'cell-oi9ikb' + }, + row: 'row-6kv2ls', + cell: 'cell-oi9ikb', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { + 'block-id': 'block-ceeddd05-330e-4f86-8017-4a3a060c4627', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-d1sv2g', + cell: 'cell-dt6ks2' + }, + row: 'row-d1sv2g', + cell: 'cell-dt6ks2', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { + 'block-id': 'block-37b19322-cb57-4e6f-8fad-0d1401cae53f', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-d1sv2g', + cell: 'cell-qah2ay' + }, + row: 'row-d1sv2g', + cell: 'cell-qah2ay', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { + 'block-id': 'block-468a69b5-9332-450b-9107-381d593de249', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-d1sv2g', + cell: 'cell-fpcz5a' + }, + row: 'row-d1sv2g', + cell: 'cell-fpcz5a', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { + 'block-id': 'block-26b1d252-9b2e-4808-9b29-04e76696aa3c', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-pflz90', + cell: 'cell-zrhylp' + }, + row: 'row-pflz90', + cell: 'cell-zrhylp', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { + 'block-id': 'block-6af97ba7-8cf9-497a-9365-7075b938837b', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-pflz90', + cell: 'cell-s1q9nt' + }, + row: 'row-pflz90', + cell: 'cell-s1q9nt', + rowspan: '1', + colspan: '1' + }) + // This attributes has only list and no table-cell-line + .insert('\n', { + list: { + rowspan: '1', + colspan: '1', + row: 'row-pflz90', + cell: 'cell-20b0j9', + list: 'bullet' + }, + 'block-id': 'block-107e273e-86bc-44fd-b0d7-41ab55aca484', + row: 'row-pflz90', + cell: 'cell-20b0j9', + rowspan: '1', + colspan: '1' + }) + // No table-cell-line below here + .insert('\n', { + 'block-id': 'block-38161f9c-6f6d-44c5-b086-54cc6490f1e3' + }) + .insert('Content after table') + .insert('\n', { + 'block-id': 'block-15630542-ef45-412d-9415-88f0052238ce' + }) + t.compare(result, expectedResult) +} + +/** + * https://github.com/yjs/yjs/issues/503 + * @param {t.TestCase} _tc + */ +export const testDeltaBug2 = _tc => { + const initialContent = delta.create() + .insert("Thomas' section") + .insert('\n', { 'block-id': 'block-61ae80ac-a469-4eae-bac9-3b6a2c380118' }) + .insert('\n', { 'block-id': 'block-d265d93f-1cc7-40ee-bb58-8270fca2619f' }) + .insert('123') + .insert('\n', { + 'block-id': 'block-592a7bee-76a3-4e28-9c25-7a84344f8813', + list: { list: 'toggled', 'toggle-id': 'list-66xfft' } + }) + .insert('456') + .insert('\n', { + indent: 1, + 'block-id': 'block-3ee2bd70-b97f-45b2-9115-f1e8910235b1', + list: { list: 'toggled', 'toggle-id': 'list-6vh0t0' } + }) + .insert('789') + .insert('\n', { + indent: 1, + 'block-id': 'block-78150cf3-9bb5-4dea-a6f5-0ce1d2a98b9c', + list: { list: 'toggled', 'toggle-id': 'list-7jr0l2' } + }) + .insert('901') + .insert('\n', { + indent: 1, + 'block-id': 'block-13c6416f-f522-41d5-9fd4-ce4eb1cde5ba', + list: { list: 'toggled', 'toggle-id': 'list-7uk8qu' } + }) + .insert([{ + slash_command: { + id: 'doc_94zq-2436', + sessionId: 'nkwc70p2j', + replace: '/' + } + }]) + .insert('\n', { 'block-id': 'block-8a1d2bb6-23c2-4bcf-af3c-3919ffea1697' }) + .insert('\n\n\n', { 'table-col': { width: '150' } }) + .insert('\n', { + 'block-id': 'block-84ec3ea4-da6a-4e03-b430-0e5f432936a9', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-blmd4s', + cell: 'cell-m0u5za' + }, + row: 'row-blmd4s', + cell: 'cell-m0u5za', + rowspan: '1', + colspan: '1' + } + ) + .insert('\n', { + 'block-id': 'block-83144ca8-aace-401e-8aa5-c05928a8ccf0', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-blmd4s', + cell: 'cell-1v8s8t' + }, + row: 'row-blmd4s', + cell: 'cell-1v8s8t', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { + 'block-id': 'block-9a493387-d27f-4b58-b2f7-731dfafda32a', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-blmd4s', + cell: 'cell-126947' + }, + row: 'row-blmd4s', + cell: 'cell-126947', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { + 'block-id': 'block-3484f86e-ae42-440f-8de6-857f0d8011ea', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-hmmljo', + cell: 'cell-wvutl9' + }, + row: 'row-hmmljo', + cell: 'cell-wvutl9', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { + 'block-id': 'block-d4e0b741-9dea-47a5-85e1-4ded0efbc89d', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-hmmljo', + cell: 'cell-nkablr' + }, + row: 'row-hmmljo', + cell: 'cell-nkablr', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { + 'block-id': 'block-352f0d5a-d1b9-422f-b136-4bacefd00b1a', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-hmmljo', + cell: 'cell-n8xtd0' + }, + row: 'row-hmmljo', + cell: 'cell-n8xtd0', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { + 'block-id': 'block-95823e57-f29c-44cf-a69d-2b4494b7144b', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-ev4xwq', + cell: 'cell-ua9bvu' + }, + row: 'row-ev4xwq', + cell: 'cell-ua9bvu', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { + 'block-id': 'block-cde5c027-15d3-4780-9e76-1e1a9d97a8e8', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-ev4xwq', + cell: 'cell-7bwuvk' + }, + row: 'row-ev4xwq', + cell: 'cell-7bwuvk', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { + 'block-id': 'block-11a23ed4-b04d-4e45-8065-8120889cd4a4', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-ev4xwq', + cell: 'cell-aouka5' + }, + row: 'row-ev4xwq', + cell: 'cell-aouka5', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { 'block-id': 'block-15b4483c-da98-4ded-91d3-c3d6ebc82582' }) + .insert([{ divider: true }]) + .insert('\n', { 'block-id': 'block-68552c8e-b57b-4f4a-9f36-6cc1ef6b3461' }) + .insert('jklasjdf') + .insert('\n', { + 'block-id': 'block-c8b2df7d-8ec5-4dd4-81f1-8d8efc40b1b4', + list: { list: 'toggled', 'toggle-id': 'list-9ss39s' } + }) + .insert('asdf') + .insert('\n', { + 'block-id': 'block-4f252ceb-14da-49ae-8cbd-69a701d18e2a', + list: { list: 'toggled', 'toggle-id': 'list-uvo013' } + }) + .insert('adg') + .insert('\n', { + 'block-id': 'block-ccb9b72e-b94d-45a0-aae4-9b0a1961c533', + list: { list: 'toggled', 'toggle-id': 'list-k53iwe' } + }) + .insert('asdfasdfasdf') + .insert('\n', { + 'block-id': 'block-ccb9b72e-b94d-45a0-aae4-9b0a1961c533', + list: { list: 'none' }, + indent: 1 + }) + .insert('asdf') + .insert('\n', [{ + 'block-id': 'block-f406f76d-f338-4261-abe7-5c9131f7f1ad', + list: { list: 'toggled', 'toggle-id': 'list-en86ur' } + }]) + .insert('\n', { 'block-id': 'block-be18141c-9b6b-434e-8fd0-2c214437d560' }) + .insert('\n', { 'block-id': 'block-36922db3-4af5-48a1-9ea4-0788b3b5d7cf' }) + .insert([{ table_content: true }]) + .insert(' ') + .insert([{ + slash_command: { + id: 'doc_94zq-2436', + sessionId: 'hiyrt6fny', + replace: '/' + } + }]) + .insert('\n', { 'block-id': 'block-9d6566a1-be55-4e20-999a-b990bc15e143' }) + .insert('\n', { + 'block-id': 'block-4b545085-114d-4d07-844c-789710ec3aab', + layout: + '12d887e1-d1a2-4814-a1a3-0c904e950b46_1185cd29-ef1b-45d5-8fda-51a70b704e64', + 'layout-width': '0.25' + }) + .insert('\n', { + 'block-id': 'block-4d3f2321-33d1-470e-9b7c-d5a683570148', + layout: + '12d887e1-d1a2-4814-a1a3-0c904e950b46_75523ea3-c67f-4f5f-a85f-ac7c8fc0a992', + 'layout-width': '0.5' + }) + .insert('\n', { + 'block-id': 'block-4c7ae1e6-758e-470f-8d7c-ae0325e4ee8a', + layout: + '12d887e1-d1a2-4814-a1a3-0c904e950b46_54c740ef-fd7b-48c6-85aa-c14e1bfc9297', + 'layout-width': '0.25' + }) + .insert('\n', { 'block-id': 'block-2d6ff0f4-ff00-42b7-a8e2-b816463d8fb5' }) + .insert([{ divider: true }]) + .insert('\n', { 'table-col': { width: '150' } }) + .insert('\n', { 'table-col': { width: '154' } }) + .insert('\n', { 'table-col': { width: '150' } }) + .insert('\n', { + 'block-id': 'block-38545d56-224b-464c-b779-51fcec24dbbf', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-q0qfck', + cell: 'cell-hmapv4' + }, + row: 'row-q0qfck', + cell: 'cell-hmapv4', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { + 'block-id': 'block-d413a094-5f52-4fd4-a4aa-00774f6fdb44', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-q0qfck', + cell: 'cell-c0czb2' + }, + row: 'row-q0qfck', + cell: 'cell-c0czb2', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { + 'block-id': 'block-ff855cbc-8871-4e0a-9ba7-de0c1c2aa585', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-q0qfck', + cell: 'cell-hcpqmm' + }, + row: 'row-q0qfck', + cell: 'cell-hcpqmm', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { + 'block-id': 'block-4841e6ee-fef8-4473-bf04-f5ba62db17f0', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-etopyl', + cell: 'cell-0io73v' + }, + row: 'row-etopyl', + cell: 'cell-0io73v', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { + 'block-id': 'block-adeec631-d4fe-4f38-9d5e-e67ba068bd24', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-etopyl', + cell: 'cell-gt2waa' + }, + row: 'row-etopyl', + cell: 'cell-gt2waa', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { + 'block-id': 'block-d38a7308-c858-4ce0-b1f3-0f9092384961', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-etopyl', + cell: 'cell-os9ksy' + }, + row: 'row-etopyl', + cell: 'cell-os9ksy', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { + 'block-id': 'block-a9df6568-1838-40d1-9d16-3c073b6ce169', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-0jwjg3', + cell: 'cell-hbx9ri' + }, + row: 'row-0jwjg3', + cell: 'cell-hbx9ri', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { + 'block-id': 'block-e26a0cf2-fe62-44a5-a4ca-8678a56d62f1', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-0jwjg3', + cell: 'cell-yg5m2w' + }, + row: 'row-0jwjg3', + cell: 'cell-yg5m2w', + rowspan: '1', + colspan: '1' + }) + .insert('a') + .insert('\n', { + 'block-id': 'block-bfbc5ac2-7417-44b9-9aa5-8e36e4095627', + list: { + rowspan: '1', + colspan: '1', + row: 'row-0jwjg3', + cell: 'cell-1azhl2', + list: 'ordered' + }, + rowspan: '1', + colspan: '1', + row: 'row-0jwjg3', + cell: 'cell-1azhl2' + }) + .insert('b') + .insert('\n', { + 'block-id': 'block-f011c089-6389-47c0-8396-7477a29aa56f', + list: { + rowspan: '1', + colspan: '1', + row: 'row-0jwjg3', + cell: 'cell-1azhl2', + list: 'ordered' + }, + rowspan: '1', + colspan: '1', + row: 'row-0jwjg3', + cell: 'cell-1azhl2' + }) + .insert('c') + .insert('\n', { + 'block-id': 'block-4497788d-1e02-4fd5-a80a-48b61a6185cb', + list: { + rowspan: '1', + colspan: '1', + row: 'row-0jwjg3', + cell: 'cell-1azhl2', + list: 'ordered' + }, + rowspan: '1', + colspan: '1', + row: 'row-0jwjg3', + cell: 'cell-1azhl2' + }) + .insert('d') + .insert('\n', { + 'block-id': 'block-5d73a2c7-f98b-47c7-a3f5-0d8527962b02', + list: { + rowspan: '1', + colspan: '1', + row: 'row-0jwjg3', + cell: 'cell-1azhl2', + list: 'ordered' + }, + rowspan: '1', + colspan: '1', + row: 'row-0jwjg3', + cell: 'cell-1azhl2' + }) + .insert('e') + .insert('\n', { + 'block-id': 'block-bfda76ee-ffdd-45db-a22e-a6707e11cf68', + list: { + rowspan: '1', + colspan: '1', + row: 'row-0jwjg3', + cell: 'cell-1azhl2', + list: 'ordered' + }, + rowspan: '1', + colspan: '1', + row: 'row-0jwjg3', + cell: 'cell-1azhl2' + }) + .insert('d') + .insert('\n', { + 'block-id': 'block-35242e64-a69d-4cdb-bd85-2a93766bfab4', + list: { + rowspan: '1', + colspan: '1', + row: 'row-0jwjg3', + cell: 'cell-1azhl2', + list: 'ordered' + }, + rowspan: '1', + colspan: '1', + row: 'row-0jwjg3', + cell: 'cell-1azhl2' + }) + .insert('f') + .insert('\n', { + 'block-id': 'block-8baa22c8-491b-4f1b-9502-44179d5ae744', + list: { + rowspan: '1', + colspan: '1', + row: 'row-0jwjg3', + cell: 'cell-1azhl2', + list: 'ordered' + }, + rowspan: '1', + colspan: '1', + row: 'row-0jwjg3', + cell: 'cell-1azhl2' + }) + .insert('\n', { 'block-id': 'block-7fa64af0-6974-4205-8cee-529f8bd46852' }) + .insert([{ divider: true }]) + .insert("Brandon's Section") + .insert('\n', { + header: 2, + 'block-id': 'block-cf49462c-2370-48ff-969d-576cb32c39a1' + }) + .insert('\n', { 'block-id': 'block-30ef8361-0dd6-4eee-b4eb-c9012d0e9070' }) + .insert([{ + slash_command: { + id: 'doc_94zq-2436', + sessionId: 'x9x08o916', + replace: '/' + } + }]) + .insert('\n', { 'block-id': 'block-166ed856-cf8c-486a-9365-f499b21d91b3' }) + .insert([{ divider: true }]) + .insert('\n', { + row: 'row-kssn15', + rowspan: '1', + colspan: '1', + 'block-id': 'block-e8079594-4559-4259-98bb-da5280e2a692', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-kssn15', + cell: 'cell-qxbksf' + }, + cell: 'cell-qxbksf' + }) + .insert('\n', { + 'block-id': 'block-70132663-14cc-4701-b5c5-eb99e875e2bd', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-kssn15', + cell: 'cell-lsohbx' + }, + cell: 'cell-lsohbx', + row: 'row-kssn15', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { + 'block-id': 'block-47a3899c-e3c5-4a7a-a8c4-46e0ae73a4fa', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-kssn15', + cell: 'cell-hner9k' + }, + cell: 'cell-hner9k', + row: 'row-kssn15', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { + 'block-id': 'block-0f9e650a-7841-412e-b4f2-5571b6d352c2', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-juxwc0', + cell: 'cell-ei4yqp' + }, + cell: 'cell-ei4yqp', + row: 'row-juxwc0', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { + 'block-id': 'block-53a158a9-8c82-4c82-9d4e-f5298257ca43', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-juxwc0', + cell: 'cell-25pf5x' + }, + cell: 'cell-25pf5x', + row: 'row-juxwc0', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { + 'block-id': 'block-da8ba35e-ce6e-4518-8605-c51d781eb07a', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-juxwc0', + cell: 'cell-m8reor' + }, + cell: 'cell-m8reor', + row: 'row-juxwc0', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { + 'block-id': 'block-2dce37c7-2978-4127-bed0-9549781babcb', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-ot4wy5', + cell: 'cell-dinh0i' + }, + cell: 'cell-dinh0i', + row: 'row-ot4wy5', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { + 'block-id': 'block-7b593f8c-4ea3-44b4-8ad9-4a0abffe759b', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-ot4wy5', + cell: 'cell-d115b2' + }, + cell: 'cell-d115b2', + row: 'row-ot4wy5', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { + 'block-id': 'block-272c28e6-2bde-4477-9d99-ce35b3045895', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-ot4wy5', + cell: 'cell-fuapvo' + }, + cell: 'cell-fuapvo', + row: 'row-ot4wy5', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { 'block-id': 'block-fbf23cab-1ce9-4ede-9953-f2f8250004cf' }) + .insert('\n', { 'block-id': 'block-c3fbb8c9-495c-40b0-b0dd-f6e33dd64b1b' }) + .insert('\n', { 'block-id': 'block-3417ad09-92a3-4a43-b5db-6dbcb0f16db4' }) + .insert('\n', { 'block-id': 'block-b9eacdce-4ba3-4e66-8b69-3eace5656057' }) + .insert('Dan Gornstein') + .insert('\n', { + 'block-id': 'block-d7c6ae0d-a17c-433e-85fd-5efc52b587fb', + header: 1 + }) + .insert('\n', { 'block-id': 'block-814521bd-0e14-4fbf-b332-799c6452a624' }) + .insert('aaa') + .insert('\n', { + 'block-id': 'block-6aaf4dcf-dc21-45c6-b723-afb25fe0f498', + list: { list: 'toggled', 'toggle-id': 'list-idl93b' } + }) + .insert('bb') + .insert('\n', { + indent: 1, + 'block-id': 'block-3dd75392-fa50-4bfb-ba6b-3b7d6bd3f1a1', + list: { list: 'toggled', 'toggle-id': 'list-mrq7j2' } + }) + .insert('ccc') + .insert('\n', { + 'block-id': 'block-2528578b-ecda-4f74-9fd7-8741d72dc8b3', + indent: 2, + list: { list: 'toggled', 'toggle-id': 'list-liu7dl' } + }) + .insert('\n', { 'block-id': 'block-18bf68c3-9ef3-4874-929c-9b6bb1a00325' }) + .insert('\n', { 'table-col': { width: '150' } }) + .insert('\n', { 'table-col': { width: '150' } }) + .insert('\n', { 'table-col': { width: '150' } }) + .insert('\n', { + 'block-id': 'block-d44e74b4-b37f-48e0-b319-6327a6295a57', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-si1nah', + cell: 'cell-cpybie' + }, + row: 'row-si1nah', + cell: 'cell-cpybie', + rowspan: '1', + colspan: '1' + }) + .insert('aaa') + .insert('\n', { + 'block-id': 'block-3e545ee9-0c9a-42d7-a4d0-833edb8087f3', + list: { + rowspan: '1', + colspan: '1', + row: 'row-si1nah', + cell: 'cell-cpybie', + list: 'toggled', + 'toggle-id': 'list-kjl2ik' + }, + rowspan: '1', + colspan: '1', + row: 'row-si1nah', + cell: 'cell-cpybie' + }) + .insert('bb') + .insert('\n', { + indent: 1, + 'block-id': 'block-5f1225ad-370f-46ab-8f1e-18b277b5095f', + list: { + rowspan: '1', + colspan: '1', + row: 'row-si1nah', + cell: 'cell-cpybie', + list: 'toggled', + 'toggle-id': 'list-eei1x5' + }, + rowspan: '1', + colspan: '1', + row: 'row-si1nah', + cell: 'cell-cpybie' + }) + .insert('ccc') + .insert('\n', { + indent: 2, + 'block-id': 'block-a77fdc11-ad24-431b-9ca2-09e32db94ac2', + list: { + rowspan: '1', + colspan: '1', + row: 'row-si1nah', + cell: 'cell-cpybie', + list: 'toggled', + 'toggle-id': 'list-30us3c' + }, + rowspan: '1', + colspan: '1', + row: 'row-si1nah', + cell: 'cell-cpybie' + }) + .insert('\n', { + 'block-id': 'block-d44e74b4-b37f-48e0-b319-6327a6295a57', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-si1nah', + cell: 'cell-cpybie' + }, + row: 'row-si1nah', + cell: 'cell-cpybie', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { + 'block-id': 'block-2c274c8a-757d-4892-8db8-1a7999f7ab51', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-si1nah', + cell: 'cell-al1z64' + }, + row: 'row-si1nah', + cell: 'cell-al1z64', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { + 'block-id': 'block-85931afe-1879-471c-bb4b-89e7bd517fe9', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-si1nah', + cell: 'cell-q186pb' + }, + row: 'row-si1nah', + cell: 'cell-q186pb', + rowspan: '1', + colspan: '1' + }) + .insert('asdfasdfasdf') + .insert('\n', { + 'block-id': 'block-6e0522e8-c1eb-4c07-98df-2b07c533a139', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-7x2d1o', + cell: 'cell-6eid2t' + }, + row: 'row-7x2d1o', + cell: 'cell-6eid2t', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { + 'block-id': 'block-4b3d0bd0-9175-45e9-955c-e8164f4b5376', + row: 'row-7x2d1o', + cell: 'cell-m1alad', + rowspan: '1', + colspan: '1', + list: { + rowspan: '1', + colspan: '1', + row: 'row-7x2d1o', + cell: 'cell-m1alad', + list: 'ordered' + } + }) + .insert('asdfasdfasdf') + .insert('\n', { + 'block-id': 'block-08610089-cb05-4366-bb1e-a0787d5b11bf', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-7x2d1o', + cell: 'cell-dm1l2p' + }, + row: 'row-7x2d1o', + cell: 'cell-dm1l2p', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { + 'block-id': 'block-c22b5125-8df3-432f-bd55-5ff456e41b4e', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-o0ujua', + cell: 'cell-82g0ca' + }, + row: 'row-o0ujua', + cell: 'cell-82g0ca', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { + 'block-id': 'block-7c6320e4-acaf-4ab4-8355-c9b00408c9c1', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-o0ujua', + cell: 'cell-wv6ozp' + }, + row: 'row-o0ujua', + cell: 'cell-wv6ozp', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { + 'block-id': 'block-d1bb7bed-e69e-4807-8d20-2d28fef8d08f', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-o0ujua', + cell: 'cell-ldt53x' + }, + row: 'row-o0ujua', + cell: 'cell-ldt53x', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { 'block-id': 'block-28f28cb8-51a2-4156-acf9-2380e1349745' }) + .insert([{ divider: true }]) + .insert('\n', { 'block-id': 'block-a1193252-c0c8-47fe-b9f6-32c8b01a1619' }) + .insert('\n', { 'table-col': { width: '150' } }) + .insert('\n\n', { 'table-col': { width: '150' } }) + .insert('/This is a test.') + .insert('\n', { + 'block-id': 'block-14188df0-a63f-4317-9a6d-91b96a7ac9fe', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-5ixdvv', + cell: 'cell-9tgyed' + }, + row: 'row-5ixdvv', + cell: 'cell-9tgyed', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { + 'block-id': 'block-7e5ba2af-9903-457d-adf4-2a79be81d823', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-5ixdvv', + cell: 'cell-xc56e9' + }, + row: 'row-5ixdvv', + cell: 'cell-xc56e9', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { + 'block-id': 'block-eb6cad93-caf7-4848-8adf-415255139268', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-5ixdvv', + cell: 'cell-xrze3u' + }, + row: 'row-5ixdvv', + cell: 'cell-xrze3u', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { + 'block-id': 'block-5bb547a2-6f71-4624-80c7-d0e1318c81a2', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-xbzv98', + cell: 'cell-lie0ng' + }, + row: 'row-xbzv98', + cell: 'cell-lie0ng', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { + 'block-id': 'block-b506de0d-efb6-4bd7-ba8e-2186cc57903e', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-xbzv98', + cell: 'cell-s9sow1' + }, + row: 'row-xbzv98', + cell: 'cell-s9sow1', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { + 'block-id': 'block-42d2ad20-5521-40e3-a88d-fe6906176e61', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-xbzv98', + cell: 'cell-nodtcj' + }, + row: 'row-xbzv98', + cell: 'cell-nodtcj', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { + 'block-id': 'block-7d3e4216-3f68-4dd6-bc77-4a9fad4ba008', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-5bqfil', + cell: 'cell-c8c0f3' + }, + row: 'row-5bqfil', + cell: 'cell-c8c0f3', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { + 'block-id': 'block-6671f221-551e-47fb-9b7d-9043b6b12cdc', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-5bqfil', + cell: 'cell-jvxxif' + }, + row: 'row-5bqfil', + cell: 'cell-jvxxif', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { + 'block-id': 'block-51e3161b-0437-4fe3-ac4f-129a93a93fc3', + 'table-cell-line': { + rowspan: '1', + colspan: '1', + row: 'row-5bqfil', + cell: 'cell-rmjpze' + }, + row: 'row-5bqfil', + cell: 'cell-rmjpze', + rowspan: '1', + colspan: '1' + }) + .insert('\n', { 'block-id': 'block-21099df0-afb2-4cd3-834d-bb37800eb06a' }) + const ydoc = new Y.Doc() + const ytext = ydoc.getText('id') + ytext.applyDelta(initialContent) + const changeEvent = delta.create().retain(90).delete(4).retain(1, { + layout: null, + 'layout-width': null, + 'block-id': 'block-9d6566a1-be55-4e20-999a-b990bc15e143' + }) + ytext.applyDelta(changeEvent) + const d = ytext.getContent() + t.compare(list.toArray(d.children)[40].toJSON(), { + type: 'insert', + insert: '\n', + format: { + 'block-id': 'block-9d6566a1-be55-4e20-999a-b990bc15e143' + } + }) +} + /** * In this test we are mainly interested in the cleanup behavior and whether the resulting delta makes sense. * It is fine if the resulting delta is not minimal. But applying the delta to a rich-text editor should result in a @@ -23,12 +1242,13 @@ export const testDeltaAfterConcurrentFormatting = tc => { */ const deltas = [] text1.observe(event => { - if (event.delta.length > 0) { + if (event.delta.children.len > 0) { deltas.push(event.delta) } }) testConnector.flushAllMessages() - t.compare(deltas, [[{ retain: 3, attributes: { bold: true } }, { retain: 2, attributes: { bold: null } }]]) + t.assert(deltas.length === 1) + t.compare(deltas[0], delta.create().retain(2, { bold: true }).retain(1).retain(1, { bold: null }).done()) } /** @@ -36,10 +1256,10 @@ export const testDeltaAfterConcurrentFormatting = tc => { */ export const testBasicInsertAndDelete = tc => { const { users, text0 } = init(tc, { users: 2 }) - let delta + let eventDelta text0.observe(event => { - delta = event.delta + eventDelta = event.delta }) text0.delete(0, 0) @@ -47,21 +1267,21 @@ export const testBasicInsertAndDelete = tc => { text0.insert(0, 'abc') t.assert(text0.toString() === 'abc', 'Basic insert works') - t.compare(delta, [{ insert: 'abc' }]) + t.compare(eventDelta, delta.create().insert('abc')) text0.delete(0, 1) t.assert(text0.toString() === 'bc', 'Basic delete works (position 0)') - t.compare(delta, [{ delete: 1 }]) + t.compare(eventDelta, delta.create().delete(1)) text0.delete(1, 1) t.assert(text0.toString() === 'b', 'Basic delete works (position 1)') - t.compare(delta, [{ retain: 1 }, { delete: 1 }]) + t.compare(eventDelta, delta.create().retain(1).delete(1)) users[0].transact(() => { text0.insert(0, '1') text0.delete(0, 1) }) - t.compare(delta, []) + t.compare(eventDelta, delta.create()) compare(users) } @@ -71,102 +1291,131 @@ export const testBasicInsertAndDelete = tc => { */ export const testBasicFormat = tc => { const { users, text0 } = init(tc, { users: 2 }) - let delta + let eventDelta text0.observe(event => { - delta = event.delta + eventDelta = event.delta }) text0.insert(0, 'abc', { bold: true }) t.assert(text0.toString() === 'abc', 'Basic insert with attributes works') - t.compare(text0.toDelta(), [{ insert: 'abc', attributes: { bold: true } }]) - t.compare(delta, [{ insert: 'abc', attributes: { bold: true } }]) + t.compare(text0.getContent(), delta.create().insert('abc', { bold: true }).done()) + t.compare(eventDelta, delta.create().insert('abc', { bold: true })) text0.delete(0, 1) t.assert(text0.toString() === 'bc', 'Basic delete on formatted works (position 0)') - t.compare(text0.toDelta(), [{ insert: 'bc', attributes: { bold: true } }]) - t.compare(delta, [{ delete: 1 }]) + t.compare(text0.getContent(), delta.create().insert('bc', { bold: true }).done()) + t.compare(eventDelta, delta.create().delete(1)) text0.delete(1, 1) t.assert(text0.toString() === 'b', 'Basic delete works (position 1)') - t.compare(text0.toDelta(), [{ insert: 'b', attributes: { bold: true } }]) - t.compare(delta, [{ retain: 1 }, { delete: 1 }]) + t.compare(text0.getContent(), delta.create().insert('b', { bold: true }).done()) + t.compare(eventDelta, delta.create().retain(1).delete(1)) text0.insert(0, 'z', { bold: true }) t.assert(text0.toString() === 'zb') - t.compare(text0.toDelta(), [{ insert: 'zb', attributes: { bold: true } }]) - t.compare(delta, [{ insert: 'z', attributes: { bold: true } }]) + t.compare(text0.getContent(), delta.create().insert('zb', { bold: true }).done()) + t.compare(eventDelta, delta.create().insert('z', { bold: true })) // @ts-ignore t.assert(text0._start.right.right.right.content.str === 'b', 'Does not insert duplicate attribute marker') text0.insert(0, 'y') t.assert(text0.toString() === 'yzb') - t.compare(text0.toDelta(), [{ insert: 'y' }, { insert: 'zb', attributes: { bold: true } }]) - t.compare(delta, [{ insert: 'y' }]) + t.compare(text0.getContent(), delta.create().insert('y').insert('zb', { bold: true }).done()) + t.compare(eventDelta, delta.create().insert('y')) text0.format(0, 2, { bold: null }) t.assert(text0.toString() === 'yzb') - t.compare(text0.toDelta(), [{ insert: 'yz' }, { insert: 'b', attributes: { bold: true } }]) - t.compare(delta, [{ retain: 1 }, { retain: 1, attributes: { bold: null } }]) + t.compare(text0.getContent(), delta.create().insert('yz').insert('b', { bold: true }).done()) + t.compare(eventDelta, delta.create().retain(1).retain(1, { bold: null })) compare(users) } /** * @param {t.TestCase} tc */ -export const testMultilineFormat = tc => { +export const testFalsyFormats = tc => { + const { users, text0 } = init(tc, { users: 2 }) + let delta = text0.getContent().toJSON().children + text0.observe(event => { + delta = event.delta.toJSON().children + }) + text0.insert(0, 'abcde', { falsy: false }) + t.compare(text0.getContent().toJSON().children, [{ type: 'insert', insert: 'abcde', format: { falsy: false } }]) + t.compare(delta, [{ type: 'insert', insert: 'abcde', format: { falsy: false } }]) + text0.format(1, 3, { falsy: true }) + t.compare(text0.getContent().toJSON().children, [{ type: 'insert', insert: 'a', format: { falsy: false } }, { type: 'insert', insert: 'bcd', format: { falsy: true } }, { type: 'insert', insert: 'e', format: { falsy: false } }]) + t.compare(delta, [{ type: 'retain', retain: 1 }, { type: 'retain', retain: 3, format: { falsy: true } }]) + text0.format(2, 1, { falsy: false }) + t.compare(text0.getContent().toJSON().children, [{ type: 'insert', insert: 'a', format: { falsy: false } }, { type: 'insert', insert: 'b', format: { falsy: true } }, { type: 'insert', insert: 'c', format: { falsy: false } }, { type: 'insert', insert: 'd', format: { falsy: true } }, { type: 'insert', insert: 'e', format: { falsy: false } }]) + t.compare(delta, [{ type: 'retain', retain: 2 }, { type: 'retain', retain: 1, format: { falsy: false } }]) + compare(users) +} + +/** + * @param {t.TestCase} _tc + */ +export const testMultilineFormat = _tc => { const ydoc = new Y.Doc() const testText = ydoc.getText('test') testText.insert(0, 'Test\nMulti-line\nFormatting') - testText.applyDelta([ - { retain: 4, attributes: { bold: true } }, - { retain: 1 }, // newline character - { retain: 10, attributes: { bold: true } }, - { retain: 1 }, // newline character - { retain: 10, attributes: { bold: true } } - ]) - t.compare(testText.toDelta(), [ - { insert: 'Test', attributes: { bold: true } }, - { insert: '\n' }, - { insert: 'Multi-line', attributes: { bold: true } }, - { insert: '\n' }, - { insert: 'Formatting', attributes: { bold: true } } - ]) + const tt = delta.create() + .retain(4, { bold: true }) + .retain(1) // newline character + .retain(10, { bold: true }) + .retain(1) // newline character + .retain(10, { bold: true }) + testText.applyDelta(tt) + t.compare( + testText.getContent(), + delta.create() + .insert('Test', { bold: true }) + .insert('\n') + .insert('Multi-line', { bold: true }) + .insert('\n') + .insert('Formatting', { bold: true }) + .done() + ) } /** - * @param {t.TestCase} tc + * @param {t.TestCase} _tc */ -export const testNotMergeEmptyLinesFormat = tc => { +export const testNotMergeEmptyLinesFormat = _tc => { const ydoc = new Y.Doc() const testText = ydoc.getText('test') - testText.applyDelta([ - { insert: 'Text' }, - { insert: '\n', attributes: { title: true } }, - { insert: '\nText' }, - { insert: '\n', attributes: { title: true } } - ]) - t.compare(testText.toDelta(), [ - { insert: 'Text' }, - { insert: '\n', attributes: { title: true } }, - { insert: '\nText' }, - { insert: '\n', attributes: { title: true } } - ]) + testText.applyDelta(delta.create() + .insert('Text') + .insert('\n', { title: true }) + .insert('\nText') + .insert('\n', { title: true }) + ) + t.compare( + testText.getContent(), + delta.create() + .insert('Text') + .insert('\n', { title: true }) + .insert('\nText') + .insert('\n', { title: true }) + .done() + ) } /** - * @param {t.TestCase} tc + * @param {t.TestCase} _tc */ -export const testPreserveAttributesThroughDelete = tc => { +export const testPreserveAttributesThroughDelete = _tc => { const ydoc = new Y.Doc() const testText = ydoc.getText('test') - testText.applyDelta([ - { insert: 'Text' }, - { insert: '\n', attributes: { title: true } }, - { insert: '\n' } - ]) - testText.applyDelta([ - { retain: 4 }, - { delete: 1 }, - { retain: 1, attributes: { title: true } } - ]) - t.compare(testText.toDelta(), [ - { insert: 'Text' }, - { insert: '\n', attributes: { title: true } } - ]) + testText.applyDelta(delta.create() + .insert('Text') + .insert('\n', { title: true }) + .insert('\n') + ) + testText.applyDelta(delta.create() + .retain(4) + .delete(1) + .retain(1, { title: true }) + ) + t.compare(testText.getContent(), + delta.create() + .insert('Text') + .insert('\n', { title: true }) + .done() + ) } /** @@ -174,12 +1423,12 @@ export const testPreserveAttributesThroughDelete = tc => { */ export const testGetDeltaWithEmbeds = tc => { const { text0 } = init(tc, { users: 1 }) - text0.applyDelta([{ - insert: { linebreak: 's' } - }]) - t.compare(text0.toDelta(), [{ - insert: { linebreak: 's' } - }]) + text0.applyDelta(delta.create().insert([{ linebreak: 's' }])) + t.compare(text0.getContent(), + delta.create() + .insert([{ linebreak: 's' }]) + .done() + ) } /** @@ -187,21 +1436,21 @@ export const testGetDeltaWithEmbeds = tc => { */ export const testTypesAsEmbed = tc => { const { text0, text1, testConnector } = init(tc, { users: 2 }) - text0.applyDelta([{ - insert: new Y.Map([['key', 'val']]) - }]) - t.compare(text0.toDelta()[0].insert.toJSON(), { key: 'val' }) + text0.applyDelta(delta.create() + .insert([new Y.Map([['key', 'val']])]) + ) + t.compare(/** @type {any} */ (text0).getContentDeep().toJSON().children, [{ type: 'insert', insert: [{ type: 'delta', attrs: { key: { type: 'insert', value: 'val' } } }] }]) let firedEvent = false text1.observe(event => { - const d = event.delta - t.assert(d.length === 1) - t.compare(d.map(x => /** @type {Y.AbstractType} */ (x.insert).toJSON()), [{ key: 'val' }]) + const d = event.deltaDeep + t.assert(d.children.len === 1) + t.compare(d.toJSON().children?.map(x => /** @type {any} */ (x).insert), [[{ type: 'delta', attrs: { key: { type: 'insert', value: 'val' } } }]]) firedEvent = true }) testConnector.flushAllMessages() - const delta = text1.toDelta() - t.assert(delta.length === 1) - t.compare(delta[0].insert.toJSON(), { key: 'val' }) + const dd = text1.getContentDeep().toJSON().children + t.assert(dd?.length === 1) + t.compare(/** @type {any} */ (dd?.[0]).insert[0], { type: 'delta', attrs: { key: { type: 'insert', value: 'val' } } }) t.assert(firedEvent, 'fired the event observer containing a Type-Embed') } @@ -212,39 +1461,33 @@ export const testSnapshot = tc => { const { text0 } = init(tc, { users: 1 }) const doc0 = /** @type {Y.Doc} */ (text0.doc) doc0.gc = false - text0.applyDelta([{ - insert: 'abcd' - }]) + text0.applyDelta(delta.create().insert('abcd')) const snapshot1 = Y.snapshot(doc0) - text0.applyDelta([{ - retain: 1 - }, { - insert: 'x' - }, { - delete: 1 - }]) + text0.applyDelta(delta.create() + .retain(1) + .insert('x') + .delete(1)) const snapshot2 = Y.snapshot(doc0) - text0.applyDelta([{ - retain: 2 - }, { - delete: 3 - }, { - insert: 'x' - }, { - delete: 1 - }]) - const state1 = text0.toDelta(snapshot1) - t.compare(state1, [{ insert: 'abcd' }]) - const state2 = text0.toDelta(snapshot2) - t.compare(state2, [{ insert: 'axcd' }]) - const state2Diff = text0.toDelta(snapshot2, snapshot1) - // @ts-ignore Remove userid info - state2Diff.forEach(v => { - if (v.attributes && v.attributes.ychange) { - delete v.attributes.ychange.user - } - }) - t.compare(state2Diff, [{ insert: 'a' }, { insert: 'x', attributes: { ychange: { type: 'added' } } }, { insert: 'b', attributes: { ychange: { type: 'removed' } } }, { insert: 'cd' }]) + text0.applyDelta(delta.create() + .retain(2) + .delete(3) + .insert('x') + .delete(1) + ) + const state1 = text0.getContent(createAttributionManagerFromSnapshots(snapshot1)) + t.compare(state1, delta.create().insert('abcd').done()) + const state2 = text0.getContent(createAttributionManagerFromSnapshots(snapshot2)) + t.compare(state2, delta.create().insert('axcd').done()) + const state2Diff = text0.getContent(createAttributionManagerFromSnapshots(snapshot1, snapshot2)) + t.compare( + state2Diff, + delta.create() + .insert('a') + .insert('x', null, { insert: [] }) + .insert('b', null, { delete: [] }) + .insert('cd') + .done() + ) } /** @@ -254,17 +1497,16 @@ export const testSnapshotDeleteAfter = tc => { const { text0 } = init(tc, { users: 1 }) const doc0 = /** @type {Y.Doc} */ (text0.doc) doc0.gc = false - text0.applyDelta([{ - insert: 'abcd' - }]) + text0.applyDelta(delta.create() + .insert('abcd') + ) const snapshot1 = Y.snapshot(doc0) - text0.applyDelta([{ - retain: 4 - }, { - insert: 'e' - }]) - const state1 = text0.toDelta(snapshot1) - t.compare(state1, [{ insert: 'abcd' }]) + text0.applyDelta(delta.create() + .retain(4) + .insert('e') + ) + const state1 = text0.getContent(createAttributionManagerFromSnapshots(snapshot1)) + t.compare(state1, delta.create().insert('abcd')) } /** @@ -283,8 +1525,15 @@ export const testToDeltaEmbedAttributes = tc => { const { text0 } = init(tc, { users: 1 }) text0.insert(0, 'ab', { bold: true }) text0.insertEmbed(1, { image: 'imageSrc.png' }, { width: 100 }) - const delta0 = text0.toDelta() - t.compare(delta0, [{ insert: 'a', attributes: { bold: true } }, { insert: { image: 'imageSrc.png' }, attributes: { width: 100 } }, { insert: 'b', attributes: { bold: true } }]) + const delta0 = text0.getContent() + t.compare( + delta0, + delta.create() + .insert('a', { bold: true }) + .insert([{ image: 'imageSrc.png' }], { width: 100 }) + .insert('b', { bold: true }) + .done() + ) } /** @@ -294,8 +1543,16 @@ export const testToDeltaEmbedNoAttributes = tc => { const { text0 } = init(tc, { users: 1 }) text0.insert(0, 'ab', { bold: true }) text0.insertEmbed(1, { image: 'imageSrc.png' }) - const delta0 = text0.toDelta() - t.compare(delta0, [{ insert: 'a', attributes: { bold: true } }, { insert: { image: 'imageSrc.png' } }, { insert: 'b', attributes: { bold: true } }], 'toDelta does not set attributes key when no attributes are present') + const delta0 = text0.getContent() + t.compare( + delta0, + delta.create() + .insert('a', { bold: true }) + .insert([{ image: 'imageSrc.png' }]) + .insert('b', { bold: true }) + .done() + , 'toDelta does not set attributes key when no attributes are present' + ) } /** @@ -305,6 +1562,7 @@ export const testFormattingRemoved = tc => { const { text0 } = init(tc, { users: 1 }) text0.insert(0, 'ab', { bold: true }) text0.delete(0, 2) + // @ts-ignore t.assert(Y.getTypeChildren(text0).length === 1) } @@ -335,7 +1593,7 @@ export const testFormattingDeltaUnnecessaryAttributeChange = tc => { }) testConnector.flushAllMessages() /** - * @type {Array} + * @type {Array>} */ const deltas = [] text0.observe(event => { @@ -346,11 +1604,9 @@ export const testFormattingDeltaUnnecessaryAttributeChange = tc => { }) text1.format(0, 1, { LIST_STYLES: 'number' }) testConnector.flushAllMessages() - const filteredDeltas = deltas.filter(d => d.length > 0) + const filteredDeltas = deltas.filter(d => d.children.len > 0) t.assert(filteredDeltas.length === 2) - t.compare(filteredDeltas[0], [ - { retain: 1, attributes: { LIST_STYLES: 'number' } } - ]) + t.compare(filteredDeltas[0], delta.create().retain(1, { LIST_STYLES: 'number' }).done()) t.compare(filteredDeltas[0], filteredDeltas[1]) } @@ -404,9 +1660,9 @@ const id = Y.createID(0, 0) const c = new Y.ContentString('a') /** - * @param {t.TestCase} tc + * @param {t.TestCase} _tc */ -export const testBestCase = tc => { +export const testBestCase = _tc => { const N = largeDocumentSize const items = new Array(N) t.measureTime('time to create two million items in the best case', () => { @@ -443,9 +1699,9 @@ const tryGc = () => { } /** - * @param {t.TestCase} tc + * @param {t.TestCase} _tc */ -export const testLargeFragmentedDocument = tc => { +export const testLargeFragmentedDocument = _tc => { const itemsToInsert = largeDocumentSize let update = /** @type {any} */ (null) ;(() => { @@ -475,9 +1731,9 @@ export const testLargeFragmentedDocument = tc => { } /** - * @param {t.TestCase} tc + * @param {t.TestCase} _tc */ -export const testIncrementalUpdatesPerformanceOnLargeFragmentedDocument = tc => { +export const testIncrementalUpdatesPerformanceOnLargeFragmentedDocument = _tc => { const itemsToInsert = largeDocumentSize const updates = /** @type {Array} */ ([]) ;(() => { @@ -585,9 +1841,9 @@ export const testSearchMarkerBug1 = tc => { /** * Reported in https://github.com/yjs/yjs/pull/32 * - * @param {t.TestCase} tc + * @param {t.TestCase} _tc */ -export const testFormattingBug = async tc => { +export const testFormattingBug = async _tc => { const ydoc1 = new Y.Doc() const ydoc2 = new Y.Doc() const text1 = ydoc1.getText() @@ -597,22 +1853,22 @@ export const testFormattingBug = async tc => { ydoc2.getText().format(1, 1, { url: 'http://docs.yjs.dev' }) Y.applyUpdate(ydoc2, Y.encodeStateAsUpdate(ydoc1)) const text2 = ydoc2.getText() - const expectedResult = [ - { insert: '\n', attributes: { url: 'http://example.com' } }, - { insert: '\n', attributes: { url: 'http://docs.yjs.dev' } }, - { insert: '\n', attributes: { url: 'http://example.com' } } - ] - t.compare(text1.toDelta(), expectedResult) - t.compare(text1.toDelta(), text2.toDelta()) - console.log(text1.toDelta()) + const expectedResult = delta.create() + .insert('\n', { url: 'http://example.com' }) + .insert('\n', { url: 'http://docs.yjs.dev' }) + .insert('\n', { url: 'http://example.com' }) + .done() + t.compare(text1.getContent(), expectedResult) + t.compare(text1.getContent().toJSON(), text2.getContent().toJSON()) + console.log(text1.getContent().toJSON()) } /** * Delete formatting should not leave redundant formatting items. * - * @param {t.TestCase} tc + * @param {t.TestCase} _tc */ -export const testDeleteFormatting = tc => { +export const testDeleteFormatting = _tc => { const doc = new Y.Doc() const text = doc.getText() text.insert(0, 'Attack ships on fire off the shoulder of Orion.') @@ -627,13 +1883,74 @@ export const testDeleteFormatting = tc => { text.format(16, 4, { bold: null }) Y.applyUpdate(doc2, Y.encodeStateAsUpdate(doc)) - const expected = [ - { insert: 'Attack ships ' }, - { insert: 'on ', attributes: { bold: true } }, - { insert: 'fire off the shoulder of Orion.' } - ] - t.compare(text.toDelta(), expected) - t.compare(text2.toDelta(), expected) + const expected = delta.create() + .insert('Attack ships ') + .insert('on ', { bold: true }) + .insert('fire off the shoulder of Orion.') + .done() + t.compare(text.getContent(), expected) + t.compare(text2.getContent(), expected) +} + +/** + * @param {t.TestCase} _tc + */ +export const testAttributedContent = _tc => { + const ydoc = new Y.Doc({ gc: false }) + const ytext = ydoc.getText() + ytext.insert(0, 'Hello World!') + let attributionManager = noAttributionsManager + + ydoc.on('afterTransaction', tr => { + // attributionManager = new TwosetAttributionManager(createIdMapFromIdSet(tr.insertSet, [new Y.Attribution('insertedAt', 42), new Y.Attribution('insert', 'kevin')]), createIdMapFromIdSet(tr.deleteSet, [new Y.Attribution('delete', 'kevin')])) + attributionManager = new TwosetAttributionManager(createIdMapFromIdSet(tr.insertSet, []), createIdMapFromIdSet(tr.deleteSet, [])) + }) + t.group('insert / delete / format', () => { + ytext.applyDelta(delta.create().retain(4, { italic: true }).retain(2).delete(5).insert('attributions')) + const expectedContent = delta.create().insert('Hell', { italic: true }, { format: { italic: [] } }).insert('o ').insert('World', {}, { delete: [] }).insert('attributions', {}, { insert: [] }).insert('!') + const attributedContent = ytext.getContent(attributionManager) + console.log(attributedContent.toJSON()) + t.assert(attributedContent.equals(expectedContent)) + }) + t.group('unformat', () => { + ytext.applyDelta(delta.create().retain(5, { italic: null })) + const expectedContent = delta.create().insert('Hell', null, { format: { italic: [] } }).insert('o attributions!') + const attributedContent = ytext.getContent(attributionManager) + console.log(attributedContent.toJSON()) + t.assert(attributedContent.equals(expectedContent)) + }) +} + +/** + * @param {t.TestCase} _tc + */ +export const testAttributedDiffing = _tc => { + const ydocVersion0 = new Y.Doc({ gc: false }) + ydocVersion0.clientID = 0 + ydocVersion0.getText().insert(0, 'Hello World!') + const ydoc = new Y.Doc({ gc: false }) + ydoc.clientID = 1 + Y.applyUpdate(ydoc, Y.encodeStateAsUpdate(ydocVersion0)) + const ytext = ydoc.getText() + ytext.applyDelta(delta.create().retain(4, { italic: true }).retain(2).delete(5).insert('attributions')) + // this represents to all insertions of ydoc + const insertionSet = Y.createInsertionSetFromStructStore(ydoc.store, false) + const deleteSet = Y.createDeleteSetFromStructStore(ydoc.store) + // exclude the changes from `ydocVersion0` + const insertionSetDiff = Y.diffIdSet(insertionSet, Y.createInsertionSetFromStructStore(ydocVersion0.store, false)) + const deleteSetDiff = Y.diffIdSet(deleteSet, Y.createDeleteSetFromStructStore(ydocVersion0.store)) + // assign attributes to the diff + const attributedInsertions = createIdMapFromIdSet(insertionSetDiff, [new Y.Attribution('insert', 'Bob')]) + const attributedDeletions = createIdMapFromIdSet(deleteSetDiff, [new Y.Attribution('delete', 'Bob')]) + // now we can define an attribution manager that maps these changes to output. One of the + // implementations is the TwosetAttributionManager + const attributionManager = new TwosetAttributionManager(attributedInsertions, attributedDeletions) + // we render the attributed content with the attributionManager + const attributedContent = ytext.getContent(attributionManager) + console.log(JSON.stringify(attributedContent.toJSON(), null, 2)) + const expectedContent = delta.create().insert('Hell', { italic: true }, { format: { italic: ['Bob'] } }).insert('o ').insert('World', {}, { delete: ['Bob'] }).insert('attributions', {}, { insert: ['Bob'] }).insert('!') + t.assert(attributedContent.equals(expectedContent)) + console.log(Y.encodeIdMap(attributedInsertions).length) } // RANDOM TESTS @@ -808,12 +2125,41 @@ const qChanges = [ const ytext = y.getText('text') const insertPos = prng.int32(gen, 0, ytext.toString().length) const text = charCounter++ + prng.word(gen) - const ops = [] - if (insertPos > 0) { - ops.push({ retain: insertPos }) + const d = delta.create() + d.retain(insertPos).insert(text).insert('\n', { 'code-block': true }) + ytext.applyDelta(d) + }, + /** + * @param {Y.Doc} y + * @param {prng.PRNG} gen + */ + (y, gen) => { // complex delta op + const ytext = y.getText('text') + const contentLen = ytext.toString().length + let currentPos = math.max(0, prng.int32(gen, 0, contentLen - 1)) + const d = delta.create().retain(currentPos) + // create max 3 ops + for (let i = 0; i < 7 && currentPos < contentLen; i++) { + prng.oneOf(gen, [ + () => { // format + const retain = math.min(prng.int32(gen, 0, contentLen - currentPos), 5) + const format = prng.oneOf(gen, marks) + d.retain(retain, format) + currentPos += retain + }, + () => { // insert + const attrs = prng.oneOf(gen, marksChoices) + const text = prng.word(gen, 1, 3) + d.insert(text, attrs) + }, + () => { // delete + const delLen = math.min(prng.int32(gen, 0, contentLen - currentPos), 10) + d.delete(delLen) + currentPos += delLen + } + ])() } - ops.push({ insert: text }, { insert: '\n', format: { 'code-block': true } }) - ytext.applyDelta(ops) + ytext.applyDelta(d) } ] @@ -822,12 +2168,9 @@ const qChanges = [ */ const checkResult = result => { for (let i = 1; i < result.testObjects.length; i++) { - /** - * @param {any} d - */ - const typeToObject = d => d.insert instanceof Y.AbstractType ? d.insert.toJSON() : d - const p1 = result.users[i].getText('text').toDelta().map(typeToObject) - const p2 = result.users[i].getText('text').toDelta().map(typeToObject) + t.info('length of text = ' + result.users[i - 1].getText('text').length) + const p1 = result.users[i - 1].getText('text').getContentDeep().children + const p2 = result.users[i].getText('text').getContentDeep().children t.compare(p1, p2) } // Uncomment this to find formatting-cleanup issues @@ -836,6 +2179,45 @@ const checkResult = result => { return result } +/** + * Generally, the content reader with attributed content works better if there is more deleted + * content. Increasing the number of deletions improves performance of getContent (even to the point + * that is beats toString(), but that might be because of internal js optimization steps). + * + * @param {t.TestCase} tc + */ +export const testAttributionManagerDefaultPerformance = tc => { + const N = 100000 + const MaxDeletionLength = 5 // 25% chance of deletion + const MaxInsertionLength = 5 + const ydoc = new Y.Doc() + const ytext = ydoc.getText() + for (let i = 0; i < N; i++) { + if (prng.bool(tc.prng) && prng.bool(tc.prng) && ytext.length > 0) { + const index = prng.int31(tc.prng, 0, ytext.length - 1) + const len = prng.int31(tc.prng, 0, math.min(ytext.length - index, MaxDeletionLength)) + ytext.delete(index, len) + } else { + const index = prng.int31(tc.prng, 0, ytext.length) + const content = prng.utf16String(tc.prng, MaxInsertionLength) + ytext.insert(index, content) + } + } + t.info(`number of changes: ${N / 1000}k`) + t.info(`length of text: ${ytext.length}`) + const M = 100 + t.measureTime(`original toString perf `, () => { + for (let i = 0; i < M; i++) { + ytext.toString() + } + }) + t.measureTime(`getContent(attributionManager) performance `, () => { + for (let i = 0; i < M; i++) { + ytext.getContent() + } + }) +} + /** * @param {t.TestCase} tc */ diff --git a/tests/y-xml.tests.js b/tests/y-xml.tests.js index 619d9ee91..41e982a35 100644 --- a/tests/y-xml.tests.js +++ b/tests/y-xml.tests.js @@ -1,7 +1,34 @@ -import { init, compare } from './testHelper.js' import * as Y from '../src/index.js' - +import { init, compare } from './testHelper.js' import * as t from 'lib0/testing' +import * as delta from 'lib0/delta' + +export const testCustomTypings = () => { + const ydoc = new Y.Doc() + const ymap = ydoc.getMap() + /** + * @type {Y.XmlElement<{ num: number, str: string, [k:string]: object|number|string }>} + */ + const yxml = ymap.set('yxml', new Y.XmlElement('test')) + /** + * @type {number|undefined} + */ + const num = yxml.getAttribute('num') + /** + * @type {string|undefined} + */ + const str = yxml.getAttribute('str') + /** + * @type {object|number|string|undefined} + */ + const dtrn = yxml.getAttribute('dtrn') + const attrs = yxml.getAttributes() + /** + * @type {object|number|string|undefined} + */ + const any = attrs.shouldBeAny + console.log({ num, str, dtrn, attrs, any }) +} /** * @param {t.TestCase} tc @@ -33,72 +60,13 @@ export const testHasProperty = tc => { } /** - * @param {t.TestCase} tc - */ -export const testEvents = tc => { - const { testConnector, users, xml0, xml1 } = init(tc, { users: 2 }) - /** - * @type {any} - */ - let event - /** - * @type {any} - */ - let remoteEvent - xml0.observe(e => { - event = e - }) - xml1.observe(e => { - remoteEvent = e - }) - xml0.setAttribute('key', 'value') - t.assert(event.attributesChanged.has('key'), 'YXmlEvent.attributesChanged on updated key') - testConnector.flushAllMessages() - t.assert(remoteEvent.attributesChanged.has('key'), 'YXmlEvent.attributesChanged on updated key (remote)') - // check attributeRemoved - xml0.removeAttribute('key') - t.assert(event.attributesChanged.has('key'), 'YXmlEvent.attributesChanged on removed attribute') - testConnector.flushAllMessages() - t.assert(remoteEvent.attributesChanged.has('key'), 'YXmlEvent.attributesChanged on removed attribute (remote)') - xml0.insert(0, [new Y.XmlText('some text')]) - t.assert(event.childListChanged, 'YXmlEvent.childListChanged on inserted element') - testConnector.flushAllMessages() - t.assert(remoteEvent.childListChanged, 'YXmlEvent.childListChanged on inserted element (remote)') - // test childRemoved - xml0.delete(0) - t.assert(event.childListChanged, 'YXmlEvent.childListChanged on deleted element') - testConnector.flushAllMessages() - t.assert(remoteEvent.childListChanged, 'YXmlEvent.childListChanged on deleted element (remote)') - compare(users) -} - -/** - * @param {t.TestCase} tc - */ -export const testTreewalker = tc => { - const { users, xml0 } = init(tc, { users: 3 }) - const paragraph1 = new Y.XmlElement('p') - const paragraph2 = new Y.XmlElement('p') - const text1 = new Y.XmlText('init') - const text2 = new Y.XmlText('text') - paragraph1.insert(0, [text1, text2]) - xml0.insert(0, [paragraph1, paragraph2, new Y.XmlElement('img')]) - const allParagraphs = xml0.querySelectorAll('p') - t.assert(allParagraphs.length === 2, 'found exactly two paragraphs') - t.assert(allParagraphs[0] === paragraph1, 'querySelectorAll found paragraph1') - t.assert(allParagraphs[1] === paragraph2, 'querySelectorAll found paragraph2') - t.assert(xml0.querySelector('p') === paragraph1, 'querySelector found paragraph1') - compare(users) -} - -/** - * @param {t.TestCase} tc + * @param {t.TestCase} _tc */ -export const testYtextAttributes = tc => { +export const testYtextAttributes = _tc => { const ydoc = new Y.Doc() const ytext = /** @type {Y.XmlText} */ (ydoc.get('', Y.XmlText)) ytext.observe(event => { - t.compare(event.changes.keys.get('test'), { action: 'add', oldValue: undefined }) + t.assert(event.delta.attrs.test?.type === 'insert') }) ytext.setAttribute('test', 42) t.compare(ytext.getAttribute('test'), 42) @@ -106,9 +74,9 @@ export const testYtextAttributes = tc => { } /** - * @param {t.TestCase} tc + * @param {t.TestCase} _tc */ -export const testSiblings = tc => { +export const testSiblings = _tc => { const ydoc = new Y.Doc() const yxml = ydoc.getXmlFragment() const first = new Y.XmlText() @@ -116,15 +84,15 @@ export const testSiblings = tc => { yxml.insert(0, [first, second]) t.assert(first.nextSibling === second) t.assert(second.prevSibling === first) - t.assert(first.parent === yxml) + t.assert(first.parent === /** @type {Y.AbstractType} */ (yxml)) t.assert(yxml.parent === null) t.assert(yxml.firstChild === first) } /** - * @param {t.TestCase} tc + * @param {t.TestCase} _tc */ -export const testInsertafter = tc => { +export const testInsertafter = _tc => { const ydoc = new Y.Doc() const yxml = ydoc.getXmlFragment() const first = new Y.XmlText() @@ -152,9 +120,9 @@ export const testInsertafter = tc => { } /** - * @param {t.TestCase} tc + * @param {t.TestCase} _tc */ -export const testClone = tc => { +export const testClone = _tc => { const ydoc = new Y.Doc() const yxml = ydoc.getXmlFragment() const first = new Y.XmlText('text') @@ -162,7 +130,6 @@ export const testClone = tc => { const third = new Y.XmlElement('p') yxml.push([first, second, third]) t.compareArrays(yxml.toArray(), [first, second, third]) - const cloneYxml = yxml.clone() ydoc.getArray('copyarr').insert(0, [cloneYxml]) t.assert(cloneYxml.length === 3) @@ -170,16 +137,265 @@ export const testClone = tc => { } /** - * @param {t.TestCase} tc + * @param {t.TestCase} _tc */ -export const testFormattingBug = tc => { +export const testFormattingBug = _tc => { const ydoc = new Y.Doc() const yxml = /** @type {Y.XmlText} */ (ydoc.get('', Y.XmlText)) - const delta = [ - { insert: 'A', attributes: { em: {}, strong: {} } }, - { insert: 'B', attributes: { em: {} } }, - { insert: 'C', attributes: { em: {}, strong: {} } } - ] - yxml.applyDelta(delta) - t.compare(yxml.toDelta(), delta) + const q = delta.create() + .insert('A', { em: {}, strong: {} }) + .insert('B', { em: {} }) + .insert('C', { em: {}, strong: {} }) + yxml.applyDelta(q) + t.compare(yxml.getContent(), q) +} + +/** + * @param {t.TestCase} _tc + */ +export const testElement = _tc => { + const ydoc = new Y.Doc() + const yxmlel = ydoc.getXmlElement() + const text1 = new Y.XmlText('text1') + const text2 = new Y.XmlText('text2') + yxmlel.insert(0, [text1, text2]) + t.compareArrays(yxmlel.toArray(), [text1, text2]) +} + +/** + * @param {t.TestCase} _tc + */ +export const testFragmentAttributedContent = _tc => { + const ydoc = new Y.Doc({ gc: false }) + const yfragment = new Y.XmlFragment() + const elem1 = new Y.XmlText('hello') + const elem2 = new Y.XmlElement() + const elem3 = new Y.XmlText('world') + yfragment.insert(0, [elem1, elem2]) + ydoc.getArray().insert(0, [yfragment]) + let attributionManager = Y.noAttributionsManager + ydoc.on('afterTransaction', tr => { + // attributionManager = new TwosetAttributionManager(createIdMapFromIdSet(tr.insertSet, [new Y.Attribution('insertedAt', 42), new Y.Attribution('insert', 'kevin')]), createIdMapFromIdSet(tr.deleteSet, [new Y.Attribution('delete', 'kevin')])) + attributionManager = new Y.TwosetAttributionManager(Y.createIdMapFromIdSet(tr.insertSet, []), Y.createIdMapFromIdSet(tr.deleteSet, [])) + }) + t.group('insert / delete', () => { + ydoc.transact(() => { + yfragment.delete(0, 1) + yfragment.insert(1, [elem3]) + }) + const expectedContent = delta.create().insert([elem1], null, { delete: [] }).insert([elem2]).insert([elem3], null, { insert: [] }) + const attributedContent = yfragment.getContent(attributionManager) + console.log(attributedContent.toJSON()) + t.assert(attributedContent.equals(expectedContent)) + t.compare(elem1.getContent(attributionManager).toJSON(), delta.create().insert('hello', null, { delete: [] }).toJSON()) + }) +} + +/** + * @param {t.TestCase} _tc + */ +export const testElementAttributedContent = _tc => { + const ydoc = new Y.Doc({ gc: false }) + const yelement = ydoc.getXmlElement('p') + const elem1 = new Y.XmlText('hello') + const elem2 = new Y.XmlElement('span') + const elem3 = new Y.XmlText('world') + yelement.insert(0, [elem1, elem2]) + let attributionManager = Y.noAttributionsManager + ydoc.on('afterTransaction', tr => { + // attributionManager = new TwosetAttributionManager(createIdMapFromIdSet(tr.insertSet, [new Y.Attribution('insertedAt', 42), new Y.Attribution('insert', 'kevin')]), createIdMapFromIdSet(tr.deleteSet, [new Y.Attribution('delete', 'kevin')])) + attributionManager = new Y.TwosetAttributionManager(Y.createIdMapFromIdSet(tr.insertSet, []), Y.createIdMapFromIdSet(tr.deleteSet, [])) + }) + t.group('insert / delete', () => { + ydoc.transact(() => { + yelement.delete(0, 1) + yelement.insert(1, [elem3]) + yelement.setAttribute('key', '42') + }) + const expectedContent = delta.create('UNDEFINED').insert([elem1], null, { delete: [] }).insert([elem2]).insert([elem3], null, { insert: [] }).set('key', '42', { insert: [] }) + const attributedContent = yelement.getContent(attributionManager) + console.log('children', attributedContent.toJSON()) + console.log('attributes', attributedContent) + t.assert(attributedContent.equals(expectedContent)) + t.compare(attributedContent.toJSON().attrs, { key: { type: 'insert', value: '42', attribution: { insert: [] } } }) + t.group('test getContentDeep', () => { + const expectedContent = delta.create('UNDEFINED') + .insert( + [delta.text().insert('hello', null, { delete: [] })], + null, + { delete: [] } + ) + .insert([delta.create('span')]) + .insert([ + delta.text().insert('world', null, { insert: [] }) + ], null, { insert: [] }) + .set('key', '42', { insert: [] }) + .done() + const attributedContent = yelement.getContentDeep(attributionManager) + console.log('children', JSON.stringify(attributedContent.toJSON().children, null, 2)) + console.log('cs expec', JSON.stringify(expectedContent.toJSON(), null, 2)) + console.log('attributes', attributedContent.toJSON().attrs) + t.assert(attributedContent.equals(expectedContent)) + t.compare(attributedContent.toJSON().attrs, { key: { type: 'insert', value: '42', attribution: { insert: [] } } }) + t.assert(attributedContent.name === 'UNDEFINED') + }) + }) +} + +/** + * @param {t.TestCase} _tc + */ +export const testElementAttributedContentViaDiffer = _tc => { + const ydocV1 = new Y.Doc() + ydocV1.getXmlElement('p').insert(0, [new Y.XmlText('hello'), new Y.XmlElement('span')]) + const ydoc = new Y.Doc() + Y.applyUpdate(ydoc, Y.encodeStateAsUpdate(ydocV1)) + const yelement = ydoc.getXmlElement('p') + const elem2 = yelement.get(1) // new Y.XmlElement('span') + const elem3 = new Y.XmlText('world') + ydoc.transact(() => { + yelement.delete(0, 1) + yelement.insert(1, [elem3]) + yelement.setAttribute('key', '42') + }) + const attributionManager = Y.createAttributionManagerFromDiff(ydocV1, ydoc) + const expectedContent = delta.create('UNDEFINED').insert([delta.create().insert('hello')], null, { delete: [] }).insert([elem2.getContentDeep()]).insert([delta.create().insert('world', null, { insert: [] })], null, { insert: [] }).set('key', '42', { insert: [] }) + const attributedContent = yelement.getContentDeep(attributionManager) + console.log('children', attributedContent.toJSON().children) + console.log('attributes', attributedContent.toJSON().attrs) + t.compare(attributedContent.toJSON(), expectedContent.toJSON()) + t.assert(attributedContent.equals(expectedContent)) + t.compare(attributedContent.toJSON().attrs, { key: { type: 'insert', value: '42', attribution: { insert: [] } } }) + t.group('test getContentDeep', () => { + const expectedContent = delta.create('UNDEFINED') + .insert( + [delta.create().insert('hello')], + null, + { delete: [] } + ) + .insert([delta.create('span')]) + .insert([ + delta.create().insert('world', null, { insert: [] }) + ], null, { insert: [] }) + .set('key', '42', { insert: [] }) + const attributedContent = yelement.getContentDeep(attributionManager) + console.log('children', JSON.stringify(attributedContent.toJSON().children, null, 2)) + console.log('cs expec', JSON.stringify(expectedContent.toJSON(), null, 2)) + console.log('attributes', attributedContent.toJSON().attrs) + t.assert(attributedContent.equals(expectedContent)) + t.compare(attributedContent.toJSON().attrs, { key: { type: 'insert', value: '42', attribution: { insert: [] } } }) + t.assert(attributedContent.name === 'UNDEFINED') + }) + ydoc.transact(() => { + elem3.insert(0, 'big') + }) + t.group('test getContentDeep after some more updates', () => { + t.info('expecting diffingAttributionManager to auto update itself') + const expectedContent = delta.create('UNDEFINED') + .insert( + [delta.create().insert('hello')], + null, + { delete: [] } + ) + .insert([delta.create('span')]) + .insert([ + delta.create().insert('bigworld', null, { insert: [] }) + ], null, { insert: [] }) + .set('key', '42', { insert: [] }) + const attributedContent = yelement.getContentDeep(attributionManager) + console.log('children', JSON.stringify(attributedContent.toJSON().children, null, 2)) + console.log('cs expec', JSON.stringify(expectedContent.toJSON(), null, 2)) + console.log('attributes', attributedContent.toJSON().attrs) + t.assert(attributedContent.equals(expectedContent)) + t.compare(attributedContent.toJSON().attrs, { key: { type: 'insert', value: '42', attribution: { insert: [] } } }) + t.assert(attributedContent.name === 'UNDEFINED') + }) + Y.applyUpdate(ydocV1, Y.encodeStateAsUpdate(ydoc)) + t.group('test getContentDeep both docs synced', () => { + t.info('expecting diffingAttributionManager to auto update itself') + const expectedContent = delta.create('UNDEFINED').insert([delta.create('span')]).insert([ + delta.create().insert('bigworld') + ]).set('key', '42') + const attributedContent = yelement.getContentDeep(attributionManager) + console.log('children', JSON.stringify(attributedContent.toJSON().children, null, 2)) + console.log('cs expec', JSON.stringify(expectedContent.toJSON(), null, 2)) + console.log('attributes', attributedContent.toJSON().attrs) + t.assert(attributedContent.equals(expectedContent)) + t.compare(attributedContent.toJSON().attrs, { key: { type: 'insert', value: '42' } }) + t.assert(attributedContent.name === 'UNDEFINED') + }) +} + +/** + * @param {t.TestCase} _tc + */ +export const testAttributionManagerSimpleExample = _tc => { + const ydoc = new Y.Doc() + ydoc.clientID = 0 + // create some initial content + ydoc.getXmlFragment().insert(0, [new Y.XmlText('hello world')]) + const ydocFork = new Y.Doc() + ydocFork.clientID = 1 + Y.applyUpdate(ydocFork, Y.encodeStateAsUpdate(ydoc)) + // modify the fork + // append a span element + ydocFork.getXmlFragment().insert(1, [new Y.XmlElement('span')]) + const ytext = /** @type {Y.XmlText} */ (ydocFork.getXmlFragment().get(0)) + // make "hello" italic + ytext.format(0, 5, { italic: true }) + ytext.insert(11, 'deleteme') + ytext.delete(11, 8) + ytext.insert(11, '!') + // highlight the changes + console.log(JSON.stringify(ydocFork.getXmlFragment().getContentDeep(Y.createAttributionManagerFromDiff(ydoc, ydocFork)), null, 2)) +/* => +{ + "children": { + "ops": [ + { + "insert": [ + { + "ops": [ + { + "insert": "hello", + "attributes": { + "italic": true + }, + "attribution": { + "attributes": { + "italic": [] -- the attribute "italic" was changed + } + } + }, + { + "insert": " world" -- "world" remains unchanged + }, + { + "insert": "!", + "attribution": { + "insert": [] -- "!" was inserted + } + } + ] + } + ] + }, + { + "insert": [ + { + "nodeName": "span", + "children": { + "ops": [] + }, + "attributes": {} + } + ], + "attribution": { + "insert": [] -- A tag was inserted + } + } + ] + } +} +*/ } diff --git a/tsconfig.json b/tsconfig.json index 31ccd70ee..e69f76521 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,64 +1,24 @@ { "compilerOptions": { - /* Basic Options */ - "target": "es2018", - "lib": ["es2018", "dom"], /* Specify library files to be included in the compilation. */ - "allowJs": true, /* Allow javascript files to be compiled. */ - "checkJs": true, /* Report errors in .js files. */ - // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ - "declaration": true, /* Generates corresponding '.d.ts' file. */ - // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ - // "sourceMap": true, /* Generates corresponding '.map' file. */ - // "outFile": "./dist/yjs.js", /* Concatenate and emit output to single file. */ - "outDir": "./dist", /* Redirect output structure to the directory. */ - "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ - // "composite": true, /* Enable project compilation */ - // "removeComments": true, /* Do not emit comments to output. */ - // "noEmit": true, /* Do not emit outputs. */ - // "importHelpers": true, /* Import emit helpers from 'tslib'. */ - // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ - // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ - - /* Strict Type-Checking Options */ - "strict": true, /* Enable all strict type-checking options. */ - "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + "target": "ES2021", + "lib": ["ES2021", "dom"], + "module": "node16", + "allowJs": true, + "checkJs": true, + "declaration": true, + "declarationMap": true, + "outDir": "./dist", + "baseUrl": "./", "emitDeclarationOnly": true, - // "strictNullChecks": true, /* Enable strict null checks. */ - // "strictFunctionTypes": true, /* Enable strict checking of function types. */ - // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ - // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ - // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ - - /* Additional Checks */ - // "noUnusedLocals": true, /* Report errors on unused locals. */ - // "noUnusedParameters": true, /* Report errors on unused parameters. */ - // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ - // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ - - /* Module Resolution Options */ - "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ - "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + "strict": true, + "noImplicitAny": true, + "moduleResolution": "nodenext", "paths": { - "yjs": ["./src/index.js"] - }, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ - // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ - // "typeRoots": [], /* List of folders to include type definitions from. */ - // "types": [], /* Type declaration files to be included in compilation. */ - // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ - "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ - // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ - - /* Source Map Options */ - // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ - // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ - - /* Experimental Options */ - // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ - // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ - // "maxNodeModuleJsDepth": 0, - // "types": ["./src/utils/typedefs.js"] + "yjs": ["./src/index.js"], + "yjs/internals": ["./src/internals.js"], + "yjs/testHelper": ["./tests/testHelper.js"] + } }, - "include": ["./src/**/*.js", "./tests/**/*.js"] + "include": ["./src/**/*.js", "./tests/**/*.js"], + "exclude": ["./node_modules/**/*"] }