From 023b2434eb3a583cd7bdc66d5096009a35fd5011 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Mon, 1 Jul 2024 11:32:47 +0800 Subject: [PATCH 01/75] feat: fix test runner in new node version --- y-octo-node/package.json | 14 +- y-octo-node/scripts/run-test.mts | 2 +- y-octo-node/yjs.d.ts | 2007 ++++++++++++++++++++++++++++++ yarn.lock | 1054 +++++++++++++--- 4 files changed, 2920 insertions(+), 157 deletions(-) create mode 100644 y-octo-node/yjs.d.ts diff --git a/y-octo-node/package.json b/y-octo-node/package.json index 4b5c403..1f404f8 100644 --- a/y-octo-node/package.json +++ b/y-octo-node/package.json @@ -25,9 +25,9 @@ "@types/prompts": "^2.4.4", "c8": "^8.0.1", "prompts": "^2.4.2", - "ts-node": "^10.9.2", + "tsx": "^4.16.2", "typescript": "^5.1.6", - "yjs": "^13.6.8" + "yjs": "^13.6.18" }, "engines": { "node": ">= 10" @@ -37,16 +37,16 @@ "build": "napi build --platform --release --no-const-enum", "build:debug": "napi build --platform --no-const-enum", "universal": "napi universal", - "test": "NODE_NO_WARNINGS=1 yarn exec ts-node-esm ./scripts/run-test.mts all", - "test:watch": "yarn exec ts-node-esm ./scripts/run-test.ts all --watch", - "test:coverage": "NODE_OPTIONS=\"--loader ts-node/esm\" c8 node ./scripts/run-test.mts all", + "test": "NODE_NO_WARNINGS=1 yarn exec tsx --no-warnings ./scripts/run-test.mts all", + "test:watch": "yarn exec tsx ./scripts/run-test.mts all --watch", + "test:coverage": "NODE_OPTIONS=\"--import tsx\" c8 node ./scripts/run-test.mts all", "version": "napi version" }, "version": "0.0.1", "sharedConfig": { "nodeArgs": [ - "--loader", - "ts-node/esm", + "--import", + "tsx", "--es-module-specifier-resolution=node" ], "env": { diff --git a/y-octo-node/scripts/run-test.mts b/y-octo-node/scripts/run-test.mts index 2ee63fe..05dba71 100755 --- a/y-octo-node/scripts/run-test.mts +++ b/y-octo-node/scripts/run-test.mts @@ -1,4 +1,4 @@ -#!/usr/bin/env ts-node-esm +#!/usr/bin/env tsx import { resolve } from "node:path"; import prompts from "prompts"; diff --git a/y-octo-node/yjs.d.ts b/y-octo-node/yjs.d.ts new file mode 100644 index 0000000..48a82fb --- /dev/null +++ b/y-octo-node/yjs.d.ts @@ -0,0 +1,2007 @@ +// Generated by dts-bundle v0.7.3 +// Dependencies for this module: +// lib0/observable +// lib0/decoding +// lib0/encoding + +import { ObservableV2 } from "lib0/observable"; +import * as decoding from "lib0/decoding"; +import * as encoding from "lib0/encoding"; + +/** + * This is an abstract interface that all Connectors should implement to keep them interchangeable. + * + * @note This interface is experimental and it is not advised to actually inherit this class. + * It just serves as typing information. + */ +export class AbstractConnector extends ObservableV2 { + constructor(ydoc: Doc, awareness: any); + doc: Doc; + awareness: any; +} + +export class DeleteItem { + constructor(clock: number, len: number); + clock: number; + len: number; +} +/** + * 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 { + clients: Map>; +} +export function iterateDeletedStructs( + transaction: Transaction, + ds: DeleteSet, + f: (arg0: InternalStruct.GC | InternalStruct.Item) => void, +): void; +export function isDeleted(ds: DeleteSet, id: ID): boolean; +export function mergeDeleteSets(dss: Array): DeleteSet; +export function createDeleteSet(): DeleteSet; +export function createDeleteSetFromStructStore(ss: StructStore): DeleteSet; +export function equalDeleteSets(ds1: DeleteSet, ds2: DeleteSet): boolean; + +/** + * A Yjs instance handles the state of shared data. + */ +export class Doc extends ObservableV2 { + constructor(opts?: DocOpts); + gc: boolean; + gcFilter: (arg0: InternalStruct.Item) => boolean; + clientID: number; + guid: string; + collectionid: string | null; + share: Map>>; + store: StructStore; + subdocs: Set; + shouldLoad: boolean; + autoLoad: boolean; + meta: any; + /** + * 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. + */ + isLoaded: boolean; + /** + * 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). + */ + isSynced: boolean; + /** + * Promise that resolves once the document has been loaded from a presistence provider. + */ + whenLoaded: Promise; + whenSynced: Promise; + /** + * Notify the parent document that you request to load data into this subdocument (if it is a subdocument). + * + * `load()` might be used in the future to request any provider to load the most current data. + * + * It is safe to call `load()` multiple times. + */ + load(): void; + getSubdocs(): Set; + getSubdocGuids(): Set; + /** + * Changes that happen inside of a transaction are bundled. This means that + * the observer fires _after_ the transaction is finished and that all changes + * that happened inside of the transaction are sent as one message to the + * other peers. + * + * @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 + * + * @public + */ + transact(f: (arg0: Transaction) => T, origin?: any): T; + /** + * Define a shared data type. + * + * Multiple calls of `ydoc.get(name, TypeConstructor)` yield the same result + * and do not overwrite each other. I.e. + * `ydoc.get(name, Y.Array) === ydoc.get(name, Y.Array)` + * + * After this method is called, the type is also available on `ydoc.share.get(name)`. + * + * *Best Practices:* + * 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 {typeof AbstractType} Type + * @example + * const ydoc = new Y.Doc(..) + * const appState = { + * document: ydoc.getText('document') + * comments: ydoc.getArray('comments') + * } + * + * @param {Type} TypeConstructor The constructor of the type definition. E.g. Y.Text, Y.Array, Y.Map, ... + * @return {InstanceType} The created type. Constructed with TypeConstructor + */ + get< + Type extends { + new (): AbstractType; + }, + >(name: string, TypeConstructor?: Type): InstanceType; + getArray(name?: string | undefined): YArray; + getText(name?: string | undefined): YText; + getMap(name?: string | undefined): YMap; + getXmlElement(name?: string | undefined): YXml.YXmlElement; + getXmlFragment(name?: string | undefined): YXml.YXmlFragment; + /** + * Converts the entire document into a js object, recursively traversing each yjs type + * Doesn't log types that have not been defined (using ydoc.getType(..)). + * + * @deprecated Do not use this method and rather call toJSON directly on the shared types. + */ + toJSON(): { + [x: string]: any; + }; +} +type DocOpts = { + /** + * Disable garbage collection (default: gc=true) + */ + gc?: boolean | undefined; + /** + * Will be called before an Item is garbage collected. Return false to keep the Item. + */ + gcFilter?: ((arg0: InternalStruct.Item) => boolean) | undefined; + /** + * Define a globally unique identifier for this document + */ + guid?: string | undefined; + /** + * Associate this document with a collection. This only plays a role if your provider has a concept of collection. + */ + collectionid?: string | null | undefined; + /** + * 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. + */ + meta?: any; + /** + * If a subdocument, automatically load document. If this is a subdocument, remote peers will load the document as well automatically. + */ + autoLoad?: boolean | undefined; + /** + * Whether the document should be synced by the provider now. This is toggled to true when you call ydoc.load() + */ + shouldLoad?: boolean | undefined; +}; +type DocEvents = { + destroy: (arg0: Doc) => void; + load: (arg0: Doc) => void; + sync: (arg0: boolean, arg1: Doc) => void; + update: (arg0: Uint8Array, arg1: any, arg2: Doc, arg3: Transaction) => void; + updateV2: (arg0: Uint8Array, arg1: any, arg2: Doc, arg3: Transaction) => void; + beforeAllTransactions: (arg0: Doc) => void; + beforeTransaction: (arg0: Transaction, arg1: Doc) => void; + beforeObserverCalls: (arg0: Transaction, arg1: Doc) => void; + afterTransaction: (arg0: Transaction, arg1: Doc) => void; + afterTransactionCleanup: (arg0: Transaction, arg1: Doc) => void; + afterAllTransactions: (arg0: Doc, arg1: Array) => void; + subdocs: ( + arg0: { + loaded: Set; + added: Set; + removed: Set; + }, + arg1: Doc, + arg2: Transaction, + ) => void; +}; + +export class DSDecoderV1 { + constructor(decoder: decoding.Decoder); + restDecoder: decoding.Decoder; + resetDsCurVal(): void; + readDsClock(): number; + readDsLen(): number; +} +export class UpdateDecoderV1 extends DSDecoderV1 { + readLeftID(): ID; + readRightID(): ID; + /** + * Read the next client id. + * Use this in favor of readID whenever possible to reduce the number of objects created. + */ + readClient(): number; + readInfo(): number; + readString(): string; + /** + * @return {boolean} isKey + */ + readParentInfo(): boolean; + readTypeRef(): number; + /** + * Write len of a struct - well suited for Opt RLE encoder. + */ + readLen(): number; + readAny(): any; + readBuf(): Uint8Array; + /** + * Legacy implementation uses JSON parse. We use any-decoding in v2. + */ + readJSON(): any; + readKey(): string; +} +export class DSDecoderV2 { + constructor(decoder: decoding.Decoder); + restDecoder: decoding.Decoder; + resetDsCurVal(): void; + readDsClock(): number; + readDsLen(): number; +} +export class UpdateDecoderV2 extends DSDecoderV2 { + /** + * List of cached keys. If the keys[id] does not exist, we read a new key + * from stringEncoder and push it to keys. + */ + keys: Array; + keyClockDecoder: decoding.IntDiffOptRleDecoder; + clientDecoder: decoding.UintOptRleDecoder; + leftClockDecoder: decoding.IntDiffOptRleDecoder; + rightClockDecoder: decoding.IntDiffOptRleDecoder; + infoDecoder: decoding.RleDecoder; + stringDecoder: decoding.StringDecoder; + parentInfoDecoder: decoding.RleDecoder; + typeRefDecoder: decoding.UintOptRleDecoder; + lenDecoder: decoding.UintOptRleDecoder; + readLeftID(): ID; + readRightID(): ID; + /** + * Read the next client id. + * Use this in favor of readID whenever possible to reduce the number of objects created. + */ + readClient(): number; + readInfo(): number; + readString(): string; + readParentInfo(): boolean; + readTypeRef(): number; + /** + * Write len of a struct - well suited for Opt RLE encoder. + */ + readLen(): number; + readAny(): any; + readBuf(): Uint8Array; + /** + * This is mainly here for legacy purposes. + * + * Initial we incoded objects using JSON. Now we use the much faster lib0/any-encoder. This method mainly exists for legacy purposes for the v1 encoder. + */ + readJSON(): any; + readKey(): string; +} + +export class DSEncoderV1 { + restEncoder: encoding.Encoder; + toUint8Array(): Uint8Array; + resetDsCurVal(): void; + writeDsClock(clock: number): void; + writeDsLen(len: number): void; +} +export class UpdateEncoderV1 extends DSEncoderV1 { + writeLeftID(id: ID): void; + writeRightID(id: ID): void; + /** + * Use writeClient and writeClock instead of writeID if possible. + */ + writeClient(client: number): void; + writeInfo(info: number): void; + writeString(s: string): void; + writeParentInfo(isYKey: boolean): void; + writeTypeRef(info: number): void; + /** + * Write len of a struct - well suited for Opt RLE encoder. + */ + writeLen(len: number): void; + writeAny(any: any): void; + writeBuf(buf: Uint8Array): void; + writeJSON(embed: any): void; + writeKey(key: string): void; +} +export class DSEncoderV2 { + restEncoder: encoding.Encoder; + dsCurrVal: number; + toUint8Array(): Uint8Array; + resetDsCurVal(): void; + /** + * @param {number} clock + */ + writeDsClock(clock: number): void; + /** + * @param {number} len + */ + writeDsLen(len: number): void; +} +export class UpdateEncoderV2 extends DSEncoderV2 { + /** + * @type {Map} + */ + keyMap: Map; + /** + * Refers to the next uniqe key-identifier to me used. + * See writeKey method for more information. + * + * @type {number} + */ + keyClock: number; + keyClockEncoder: encoding.IntDiffOptRleEncoder; + clientEncoder: encoding.UintOptRleEncoder; + leftClockEncoder: encoding.IntDiffOptRleEncoder; + rightClockEncoder: encoding.IntDiffOptRleEncoder; + infoEncoder: encoding.RleEncoder; + stringEncoder: encoding.StringEncoder; + parentInfoEncoder: encoding.RleEncoder; + typeRefEncoder: encoding.UintOptRleEncoder; + lenEncoder: encoding.UintOptRleEncoder; + writeLeftID(id: ID): void; + writeRightID(id: ID): void; + writeClient(client: number): void; + writeInfo(info: number): void; + writeString(s: string): void; + writeParentInfo(isYKey: boolean): void; + writeTypeRef(info: number): void; + /** + * Write len of a struct - well suited for Opt RLE encoder. + */ + writeLen(len: number): void; + writeAny(any: any): void; + writeBuf(buf: Uint8Array): void; + /** + * This is mainly here for legacy purposes. + * + * Initial we incoded objects using JSON. Now we use the much faster lib0/any-encoder. This method mainly exists for legacy purposes for the v1 encoder. + */ + writeJSON(embed: any): void; + /** + * Property keys are often reused. For example, in y-prosemirror the key `bold` might + * occur very often. For a 3d application, the key `position` might occur very often. + * + * We cache these keys in a Map and refer to them via a unique number. + */ + writeKey(key: string): void; +} + +export function readUpdateV2( + decoder: decoding.Decoder, + ydoc: Doc, + transactionOrigin?: any, + structDecoder?: UpdateDecoderV1 | UpdateDecoderV2 | undefined, +): void; +export function readUpdate( + decoder: decoding.Decoder, + ydoc: Doc, + transactionOrigin?: any, +): void; +export function applyUpdateV2( + ydoc: Doc, + update: Uint8Array, + transactionOrigin?: any, + YDecoder?: typeof UpdateDecoderV1 | typeof UpdateDecoderV2 | undefined, +): void; +export function applyUpdate( + ydoc: Doc, + update: Uint8Array, + transactionOrigin?: any, +): void; +export function encodeStateAsUpdateV2( + doc: Doc, + encodedTargetStateVector?: Uint8Array | undefined, + encoder?: UpdateEncoderV2 | UpdateEncoderV1 | undefined, +): Uint8Array; +export function encodeStateAsUpdate( + doc: Doc, + encodedTargetStateVector?: Uint8Array | undefined, +): Uint8Array; +export function decodeStateVector( + decodedState: Uint8Array, +): Map; +export function encodeStateVectorV2( + doc: Doc | Map, + encoder?: DSEncoderV1 | DSEncoderV2 | undefined, +): Uint8Array; +export function encodeStateVector(doc: Doc | Map): Uint8Array; + +export class ID { + /** + * @param {number} client client id + * @param {number} clock unique per client id, continuous number + */ + constructor(client: number, clock: number); + /** + * Client id + */ + client: number; + /** + * unique per client id, continuous number + */ + clock: number; +} +export function compareIDs(a: ID | null, b: ID | null): boolean; +export function createID(client: number, clock: number): ID; +export function findRootTypeKey(type: AbstractType): string; + +export function isParentOf( + parent: AbstractType, + child: InternalStruct.Item | null, +): boolean; + +export function logType(type: AbstractType): void; + +export class PermanentUserData { + constructor(doc: Doc, storeType?: YMap | undefined); + yusers: YMap; + doc: Doc; + /** + * Maps from clientid to userDescription + */ + clients: Map; + dss: Map; + setUserMapping( + doc: Doc, + clientid: number, + userDescription: string, + conf?: { + filter?: ((arg0: Transaction, arg1: DeleteSet) => boolean) | undefined; + }, + ): void; + getUserByClientId(clientid: number): any; + getUserByDeletedId(id: ID): string | null; +} +declare namespace RelativePosition { + /** + * A relative position is based on the Yjs model and is not affected by document changes. + * E.g. If you place a relative position before a certain character, it will always point to this character. + * If you place a relative position at the end of a type, it will always point to the end of the type. + * + * A numeric position is often unsuited for user selections, because it does not change when content is inserted + * before or after. + * + * ```Insert(0, 'x')('a|bc') = 'xa|bc'``` Where | is the relative position. + * + * One of the properties must be defined. + * + * @example + * // Current cursor position is at position 10 + * const relativePosition = createRelativePositionFromIndex(yText, 10) + * // modify yText + * yText.insert(0, 'abc') + * yText.delete(3, 10) + * // Compute the cursor position + * const absolutePosition = createAbsolutePositionFromRelativePosition(y, relativePosition) + * absolutePosition.type === yText // => true + * console.log('cursor location is ' + absolutePosition.index) // => cursor location is 3 + * + */ + export class RelativePosition { + constructor( + type: ID | null, + tname: string | null, + item: ID | null, + assoc?: number, + ); + type: ID | null; + tname: string | null; + item: ID | null; + /** + * A relative position is associated to a specific character. By default + * assoc >= 0, the relative position is associated to the character + * 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 + * before the meant position. + */ + assoc: number; + } + export function relativePositionToJSON(rpos: RelativePosition): any; + export function createRelativePositionFromJSON(json: any): RelativePosition; + export class AbsolutePosition { + constructor( + type: AbstractType, + index: number, + assoc?: number | undefined, + ); + type: AbstractType; + index: number; + assoc: number; + } + export function createAbsolutePosition( + type: AbstractType, + index: number, + assoc?: number | undefined, + ): AbsolutePosition; + export function createRelativePosition( + type: AbstractType, + item: ID | null, + assoc?: number | undefined, + ): RelativePosition; + export function createRelativePositionFromTypeIndex( + type: AbstractType, + index: number, + assoc?: number | undefined, + ): RelativePosition; + export function encodeRelativePosition(rpos: RelativePosition): Uint8Array; + export function decodeRelativePosition( + uint8Array: Uint8Array, + ): RelativePosition; + export function createAbsolutePositionFromRelativePosition( + rpos: RelativePosition, + doc: Doc, + followUndoneDeletions?: boolean, + ): AbsolutePosition | null; + export function compareRelativePositions( + a: RelativePosition | null, + b: RelativePosition | null, + ): boolean; +} +declare namespace Snapshot { + export class Snapshot { + constructor(ds: DeleteSet, sv: Map); + ds: DeleteSet; + /** + * State Map + */ + sv: Map; + } + export function equalSnapshots(snap1: Snapshot, snap2: Snapshot): boolean; + export function encodeSnapshotV2( + snapshot: Snapshot, + encoder?: DSEncoderV1 | DSEncoderV2 | undefined, + ): Uint8Array; + export function encodeSnapshot(snapshot: Snapshot): Uint8Array; + export function decodeSnapshotV2( + buf: Uint8Array, + decoder?: DSDecoderV1 | DSDecoderV2 | undefined, + ): Snapshot; + export function decodeSnapshot(buf: Uint8Array): Snapshot; + export function createSnapshot( + ds: DeleteSet, + sm: Map, + ): Snapshot; + export const emptySnapshot: Snapshot; + export function snapshot(doc: Doc): Snapshot; + export function createDocFromSnapshot( + originDoc: Doc, + snapshot: Snapshot, + newDoc?: Doc | undefined, + ): Doc; + export function snapshotContainsUpdate( + snapshot: Snapshot, + update: Uint8Array, + ): boolean; + + export function typeListToArraySnapshot( + type: AbstractType, + snapshot: Snapshot, + ): Array; + export function typeMapGetSnapshot( + parent: AbstractType, + key: string, + snapshot: Snapshot, + ): + | { + [x: string]: any; + } + | number + | null + | Array + | string + | Uint8Array + | AbstractType + | undefined; + export function typeMapGetAllSnapshot( + parent: AbstractType, + snapshot: Snapshot, + ): { + [x: string]: + | { + [x: string]: any; + } + | number + | null + | Array + | string + | Uint8Array + | AbstractType + | undefined; + }; +} + +export class StructStore { + clients: Map>; + pendingStructs: { + missing: Map; + update: Uint8Array; + } | null; + pendingDs: null | Uint8Array; +} +export function getState(store: StructStore, client: number): number; +export function findIndexSS( + structs: Array, + clock: number, +): number; +export function getItem(arg0: StructStore, arg1: ID): InternalStruct.Item; + +/** + * A transaction is created for every change on the Yjs model. It is possible + * to bundle changes on the Yjs model in a single transaction to + * minimize the number on messages sent and the number of observer calls. + * If possible the user of this library should bundle as many changes as + * possible. Here is an example to illustrate the advantages of bundling: + * + * @example + * const ydoc = new Y.Doc() + * const map = ydoc.getMap('map') + * // Log content when change is triggered + * map.observe(() => { + * console.log('change triggered') + * }) + * // Each change on the map type triggers a log message: + * 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: + * ydoc.transact(() => { + * map.set('a', 1) + * map.set('b', 1) + * }) // => "change triggered" + */ +export class Transaction { + constructor(doc: Doc, origin: any, local: boolean); + /** + * The Yjs instance. + */ + doc: Doc; + /** + * Describes the set of deleted items by ids + */ + deleteSet: DeleteSet; + /** + * Holds the state before the transaction started. + */ + beforeState: Map; + /** + * Holds the state after the transaction. + */ + afterState: Map; + /** + * 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) + */ + changed: Map>, Set>; + /** + * Stores the events for the types that observe also child elements. + * It is mainly used by `observeDeep`. + */ + changedParentTypes: Map>, Array>>; + origin: any; + /** + * Stores meta information on the transaction + */ + meta: Map; + /** + * Whether this change originates from this doc. + */ + local: boolean; + subdocsAdded: Set; + subdocsRemoved: Set; + subdocsLoaded: Set; +} + +export function tryGc( + ds: DeleteSet, + store: StructStore, + gcFilter: (arg0: InternalStruct.Item) => boolean, +): void; +export function transact( + doc: Doc, + f: (arg0: Transaction) => T, + origin?: any, + local?: boolean, +): T; +declare namespace UndoManager { + export class StackItem { + constructor(deletions: DeleteSet, insertions: DeleteSet); + insertions: DeleteSet; + deletions: DeleteSet; + /** + * Use this to save and restore metadata like selection range + */ + meta: Map; + } + /** + * 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 + * metadata property on `event.stackItem.meta` (it is a `Map` of metadata properties). + * 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`. + */ + export class UndoManager extends ObservableV2<{ + "stack-item-added": (arg0: StackItemEvent, arg1: UndoManager) => void; + "stack-item-popped": (arg0: StackItemEvent, arg1: UndoManager) => void; + "stack-cleared": (arg0: { + undoStackCleared: boolean; + redoStackCleared: boolean; + }) => void; + "stack-item-updated": (arg0: StackItemEvent, arg1: UndoManager) => void; + }> { + /** + * @param {AbstractType|Array>} typeScope Accepts either a single type, or an array of types + * @param {UndoManagerOptions} options + */ + constructor( + typeScope: AbstractType | Array>, + options?: UndoManagerOptions, + ); + /** + * @type {Array>} + */ + scope: Array>; + doc: Doc; + deleteFilter: (arg0: InternalStruct.Item) => boolean; + trackedOrigins: Set; + captureTransaction: (arg0: Transaction) => boolean; + undoStack: Array; + redoStack: Array; + /** + * Whether the client is currently undoing (calling UndoManager.undo) + */ + undoing: boolean; + redoing: boolean; + /** + * The currently popped stack item if UndoManager.undoing or UndoManager.redoing + */ + currStackItem: StackItem | null; + lastChange: number; + ignoreRemoteMapChanges: boolean; + captureTimeout: number; + afterTransactionHandler: (transaction: Transaction) => void; + addToScope(ytypes: Array> | AbstractType): void; + addTrackedOrigin(origin: any): void; + removeTrackedOrigin(origin: any): void; + clear(clearUndoStack?: boolean, clearRedoStack?: boolean): void; + /** + * UndoManager merges Undo-StackItem if they are created within time-gap + * smaller than `options.captureTimeout`. Call `um.stopCapturing()` so that the next + * StackItem won't be merged. + * + * + * @example + * // without stopCapturing + * ytext.insert(0, 'a') + * ytext.insert(1, 'b') + * um.undo() + * ytext.toString() // => '' (note that 'ab' was removed) + * // with stopCapturing + * ytext.insert(0, 'a') + * um.stopCapturing() + * ytext.insert(0, 'b') + * um.undo() + * ytext.toString() // => 'a' (note that only 'b' was removed) + * + */ + stopCapturing(): void; + /** + * Undo last changes on type. + */ + undo(): StackItem | null; + /** + * Redo last undo operation. + */ + redo(): StackItem | null; + /** + * Are undo steps available? + */ + canUndo(): boolean; + /** + * Are redo steps available? + */ + canRedo(): boolean; + } + type UndoManagerOptions = { + captureTimeout?: number | undefined; + /** + * Do not capture changes of a Transaction if result false. + */ + captureTransaction?: ((arg0: Transaction) => boolean) | undefined; + /** + * Sometimes + * it is necessary to filter what an Undo/Redo operation can delete. If this + * filter returns false, the type/item won't be deleted even it is in the + * undo/redo scope. + */ + deleteFilter?: ((arg0: InternalStruct.Item) => boolean) | undefined; + trackedOrigins?: Set | undefined; + /** + * Experimental. By default, the UndoManager will never overwrite remote changes. Enable this property to enable overwriting remote changes on key-value changes (Y.Map, properties on Y.Xml, etc..). + */ + ignoreRemoteMapChanges?: boolean | undefined; + /** + * The document that this UndoManager operates on. Only needed if typeScope is empty. + */ + doc?: Doc | undefined; + }; + type StackItemEvent = { + stackItem: StackItem; + origin: any; + type: "undo" | "redo"; + changedParentTypes: Map>, Array>>; + }; +} + +export function logUpdate(update: Uint8Array): void; +export function logUpdateV2( + update: Uint8Array, + YDecoder?: typeof UpdateDecoderV1 | typeof UpdateDecoderV2 | undefined, +): void; +export function decodeUpdate(update: Uint8Array): { + structs: (InternalStruct.GC | InternalStruct.Item | InternalStruct.Skip)[]; + ds: DeleteSet; +}; +export function decodeUpdateV2( + update: Uint8Array, + YDecoder?: typeof UpdateDecoderV1 | typeof UpdateDecoderV2 | undefined, +): { + structs: (InternalStruct.GC | InternalStruct.Item | InternalStruct.Skip)[]; + ds: DeleteSet; +}; + +export function mergeUpdates(updates: Array): Uint8Array; +export function encodeStateVectorFromUpdateV2( + update: Uint8Array, + YEncoder?: typeof DSEncoderV1 | typeof DSEncoderV2, + YDecoder?: typeof UpdateDecoderV1 | typeof UpdateDecoderV2, +): Uint8Array; +export function encodeStateVectorFromUpdate(update: Uint8Array): Uint8Array; +export function parseUpdateMetaV2( + update: Uint8Array, + YDecoder?: typeof UpdateDecoderV1 | typeof UpdateDecoderV2, +): { + from: Map; + to: Map; +}; +export function parseUpdateMeta(update: Uint8Array): { + from: Map; + to: Map; +}; +export function mergeUpdatesV2( + updates: Array, + YDecoder?: typeof UpdateDecoderV1 | typeof UpdateDecoderV2 | undefined, + YEncoder?: typeof UpdateEncoderV2 | typeof UpdateEncoderV1 | undefined, +): Uint8Array; +export function diffUpdateV2( + update: Uint8Array, + sv: Uint8Array, + YDecoder?: typeof UpdateDecoderV1 | typeof UpdateDecoderV2 | undefined, + YEncoder?: typeof UpdateEncoderV2 | typeof UpdateEncoderV1 | undefined, +): Uint8Array; +export function diffUpdate(update: Uint8Array, sv: Uint8Array): Uint8Array; +export function obfuscateUpdate( + update: Uint8Array, + opts?: ObfuscatorOptions | undefined, +): Uint8Array; +export function obfuscateUpdateV2( + update: Uint8Array, + opts?: ObfuscatorOptions | undefined, +): Uint8Array; +export function convertUpdateFormatV1ToV2(update: Uint8Array): Uint8Array; +export function convertUpdateFormatV2ToV1(update: Uint8Array): Uint8Array; +type ObfuscatorOptions = { + formatting?: boolean | undefined; + subdocs?: boolean | undefined; + /** + * Whether to obfuscate nodeName / hookName + */ + yxml?: boolean | undefined; +}; + +/** + * YEvent describes the changes on a YType. + */ +export class YEvent> { + /** + * @param {T} target The changed type. + * @param {Transaction} transaction + */ + constructor(target: T, transaction: Transaction); + /** + * The type on which this event was created on. + */ + target: T; + /** + * The current target on which the observe callback is called. + */ + currentTarget: AbstractType; + /** + * The transaction that triggered this event. + */ + transaction: Transaction; + /** + * Computes the path from `y` to the changed type. + * + * @todo v14 should standardize on path: Array<{parent, index}> because that is easier to work with. + * + * The following property holds: + * @example + * let type = y + * event.path.forEach(dir => { + * type = type.get(dir) + * }) + * type === event.target // => true + */ + get path(): (string | number)[]; + /** + * Check if a struct is deleted by this event. + * + * In contrast to change.deleted, this method also returns true if the struct was added and then deleted. + */ + deletes(struct: InternalStruct.AbstractStruct): boolean; + get keys(): Map< + string, + { + action: "add" | "update" | "delete"; + oldValue: any; + newValue: any; + } + >; + /** + * This is a computed property. Note that this can only be safely computed during the + * event call. Computing this property after other changes happened might result in + * unexpected behavior (incorrect computation of deltas). A safe way to collect changes + * is to store the `changes` or the `delta` object. Avoid storing the `transaction` object. + */ + get delta(): { + insert?: string | object | any[] | AbstractType | undefined; + retain?: number | undefined; + delete?: number | undefined; + attributes?: + | { + [x: string]: any; + } + | undefined; + }[]; + /** + * 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. + */ + adds(struct: InternalStruct.AbstractStruct): boolean; + /** + * This is a computed property. Note that this can only be safely computed during the + * event call. Computing this property after other changes happened might result in + * unexpected behavior (incorrect computation of deltas). A safe way to collect changes + * is to store the `changes` or the `delta` object. Avoid storing the `transaction` object. + */ + get changes(): { + added: Set; + deleted: Set; + keys: Map< + string, + { + action: "add" | "update" | "delete"; + oldValue: any; + } + >; + delta: Array<{ + insert?: Array | string; + delete?: number; + retain?: number; + }>; + }; +} + +export function getTypeChildren( + t: AbstractType, +): Array; + +/** + * Abstract Yjs Type class + */ +export class AbstractType { + doc: Doc | null; + get parent(): AbstractType | null; + + /** + * 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. + */ + clone(): AbstractType; + /** + * Observe all events that are created on this type. + */ + observe(f: (arg0: EventType, arg1: Transaction) => void): void; + /** + * Observe all events that are created by this type and its children. + */ + observeDeep(f: (arg0: Array>, arg1: Transaction) => void): void; + /** + * Unregister an observer function. + */ + unobserve(f: (arg0: EventType, arg1: Transaction) => void): void; + /** + * Unregister an observer function. + */ + unobserveDeep(f: (arg0: Array>, arg1: Transaction) => void): void; + toJSON(): any; +} + +/** + * Event that describes the changes on a YArray + */ +export class YArrayEvent extends YEvent> { + constructor(target: YArray, transaction: Transaction); +} +/** + * A shared Array implementation. + */ +export class YArray + extends AbstractType> + implements Iterable +{ + /** + * Construct a new YArray containing the specified items. + * @template {Object|Array|number|null|string|Uint8Array} T + * @param {Array} items + * @return {YArray} + */ + static from< + T_1 extends + | string + | number + | any[] + | Uint8Array + | { + [x: string]: any; + } + | null, + >(items: T_1[]): 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(): YArray; + get length(): number; + /** + * Inserts new content at an index. + * + * Important: This function expects an array of content. Not just a content + * object. The reason for this "weirdness" is that inserting several elements + * is very efficient when it is done as a single operation. + * + * @example + * // Insert character 'a' at position 0 + * yarray.insert(0, ['a']) + * // Insert numbers 1, 2 at position 1 + * yarray.insert(1, [1, 2]) + * + * @param {number} index The index to insert content at. + * @param {Array} content The array of content + */ + insert(index: number, content: Array): void; + /** + * Appends content to this YArray. + * + * @param {Array} content Array of content to append. + * + * @todo Use the following implementation in all types. + */ + push(content: Array): void; + /** + * Prepends content to this YArray. + * + * @param {Array} content Array of content to prepend. + */ + unshift(content: Array): void; + /** + * Deletes elements starting from an index. + * + * @param {number} index Index at which to start deleting elements + * @param {number} length The number of elements to remove. Defaults to 1. + */ + delete(index: number, length?: number): void; + /** + * Returns the i-th element from a YArray. + * + * @param {number} index The index of the element to return from the YArray + * @return {T} + */ + get(index: number): T; + /** + * Transforms this YArray to a JavaScript Array. + * + * @return {Array} + */ + toArray(): Array; + /** + * Returns a portion of this YArray into a JavaScript Array selected + * from start to end (end not included). + * + * @param {number} [start] + * @param {number} [end] + * @return {Array} + */ + slice(start?: number | undefined, end?: number | undefined): Array; + /** + * Transforms this Shared Type to a JSON object. + * + * @return {Array} + */ + toJSON(): Array; + /** + * Returns an Array with the result of calling a provided function on every + * element of this YArray. + * + * @template M + * @param {function(T,number,YArray):M} f Function that produces an element of the new Array + * @return {Array} A new array with each element being the result of the + * callback function + */ + map(f: (arg0: T, arg1: number, arg2: YArray) => M): M[]; + /** + * 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. + */ + forEach(f: (arg0: T, arg1: number, arg2: YArray) => void): void; + /** + * @return {IterableIterator} + */ + [Symbol.iterator](): IterableIterator; +} + +/** + * 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: YMap, transaction: Transaction, subs: Set); + keysChanged: Set; +} +/** + * A shared Map implementation. + */ +export class YMap + extends AbstractType> + implements Iterable<[string, MapType]> +{ + /** + * + * @param {Iterable=} entries - an optional iterable to initialize the YMap + */ + constructor(entries?: Iterable | undefined); + /** + * 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. + */ + clone(): YMap; + /** + * Transforms this Shared Type to a JSON object. + */ + toJSON(): { + [x: string]: any; + }; + /** + * Returns the size of the YMap (count of key/value pairs) + */ + get size(): number; + /** + * Returns the keys for each element in the YMap Type. + */ + keys(): IterableIterator; + /** + * Returns the values for each element in the YMap Type. + */ + values(): IterableIterator; + /** + * Returns an Iterator of [key, value] pairs + */ + entries(): IterableIterator<[string, MapType]>; + /** + * Executes a provided function on once on every key-value pair. + * + * @param {function(MapType,string,YMap):void} f A function to execute on every element of this YArray. + */ + forEach(f: (arg0: MapType, arg1: string, arg2: YMap) => void): void; + /** + * Remove a specified element from this YMap. + * + * @param {string} key The key of the element to remove. + */ + delete(key: string): void; + /** + * 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 {VAL} value The value of the element to add + * @return {VAL} + */ + set(key: string, value: VAL): VAL; + /** + * Returns a specified element from this YMap. + * + * @param {string} key + * @return {MapType|undefined} + */ + get(key: string): MapType | undefined; + /** + * Returns a boolean indicating whether the specified key exists or not. + * + * @param {string} key The key to test. + * @return {boolean} + */ + has(key: string): boolean; + /** + * Removes all elements from this YMap. + */ + clear(): void; + /** + * Returns an Iterator of [key, value] pairs + * + * @return {IterableIterator<[string, MapType]>} + */ + [Symbol.iterator](): IterableIterator<[string, MapType]>; +} + +export function cleanupYTextFormatting(type: YText): number; + +/** + * 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: YText, transaction: Transaction, subs: Set); + /** + * Set of all changed attributes. + */ + keysChanged: Set; + /** + * Compute the changes in the delta format. + * A {@link https://quilljs.com/docs/delta/|Quill Delta}) that represents the changes on the document. + */ + get delta(): { + insert?: string | object | AbstractType | undefined; + delete?: number | undefined; + retain?: number | undefined; + attributes?: + | { + [x: string]: any; + } + | undefined; + }[]; +} +/** + * Type that represents text with formatting information. + * + * This type replaces y-richtext as this implementation is able to handle + * block formats (format information on a paragraph), embeds (complex elements + * like pictures and videos), and text formats (**bold**, *italic*). + */ +export class YText extends AbstractType { + /** + * @param {String} [string] The initial value of the YText. + */ + constructor(string?: string | undefined); + /** + * Number of characters of this text type. + */ + get length(): number; + /** + * 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. + */ + clone(): YText; + /** + * Returns the unformatted string representation of this YText type. + */ + toJSON(): string; + /** + * 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. + */ + applyDelta(delta: any, opts?: { sanitize?: boolean | undefined }): void; + /** + * Returns the Delta representation of this YText type. + */ + toDelta( + snapshot?: Snapshot.Snapshot | undefined, + prevSnapshot?: Snapshot.Snapshot | undefined, + computeYChange?: ((arg0: "removed" | "added", arg1: ID) => any) | undefined, + ): any; + /** + * Insert text at a given index. + * + * @param {number} index The index at which to start inserting. + * @param {String} text The text to insert at the specified position. + * @param {TextAttributes} [attributes] Optionally define some formatting + * information to apply on the inserted + * Text. + * @public + */ + insert(index: number, text: string, attributes?: Object | undefined): void; + /** + * Inserts an embed at a index. + * + * @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 + * embed + */ + insertEmbed( + index: number, + embed: Object | AbstractType, + attributes?: Object | undefined, + ): void; + /** + * Deletes text starting from an index. + * + * @param {number} index Index at which to start deleting. + * @param {number} length The number of characters to remove. Defaults to 1. + * + * @public + */ + delete(index: number, length: number): void; + /** + * Assigns properties to a range of text. + * + * @param {number} index The position where to start formatting. + * @param {number} length The amount of characters to assign properties to. + * @param {TextAttributes} attributes Attribute information to apply on the + * text. + */ + format(index: number, length: number, attributes: TextAttributes): void; + /** + * Removes an attribute. + * + * @note Xml-Text nodes don't have attributes. You can use this feature to assign properties to complete text-blocks. + * + * @param {String} attributeName The attribute name that is to be removed. + */ + removeAttribute(attributeName: string): void; + /** + * Sets or updates an attribute. + * + * @note Xml-Text nodes don't have attributes. You can use this feature to assign properties to complete text-blocks. + * + * @param {String} attributeName The attribute name that is to be set. + * @param {any} attributeValue The attribute value that is to be set. + */ + setAttribute(attributeName: string, attributeValue: any): void; + /** + * Returns an attribute value that belongs to the attribute name. + * + * @note Xml-Text nodes don't have attributes. You can use this feature to assign properties to complete text-blocks. + * + * @param {String} attributeName The attribute name that identifies the + * queried value. + * @return {any} The queried attribute value. + */ + getAttribute(attributeName: string): any; + /** + * Returns all attribute name/value pairs in a JSON Object. + * + * @note Xml-Text nodes don't have attributes. You can use this feature to assign properties to complete text-blocks. + * + * @return {Object} A JSON Object that describes the attributes. + */ + getAttributes(): { + [x: string]: any; + }; +} +/** + * Attributes that can be assigned to a selection of text. + */ +export type TextAttributes = Object; + +declare namespace YXml { + /** + * Represents a subset of the nodes of a YXmlElement / YXmlFragment and a + * position within them. + * + * Can be created with {@link YXmlFragment#createTreeWalker} + */ + export class YXmlTreeWalker + implements Iterable + { + constructor( + root: YXmlFragment | YXmlElement, + f?: ((arg0: AbstractType) => boolean) | undefined, + ); + /** + * Get the next node. + */ + next(): IteratorResult; + [Symbol.iterator](): YXmlTreeWalker; + } + /** + * Represents a list of {@link YXmlElement}.and {@link YXmlText} types. + * A YxmlFragment is similar to a {@link YXmlElement}, but it does not have a + * nodeName and it does not have attributes. Though it can be bound to a DOM + * element - in this case the attributes and the nodeName are not shared. + */ + export class YXmlFragment extends AbstractType { + constructor(); + get firstChild(): + | YXmlElement<{ + [key: string]: string; + }> + | YXmlText + | null; + /** + * 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. + */ + clone(): YXmlFragment; + get length(): number; + /** + * 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. + */ + createTreeWalker( + filter: (arg0: AbstractType) => boolean, + ): YXmlTreeWalker; + /** + * 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. + */ + querySelector( + query: CSS_Selector, + ): YXmlElement | YXmlText | YXmlHook | null; + /** + * 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. + */ + querySelectorAll( + query: CSS_Selector, + ): Array; + toJSON(): string; + /** + * 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} + */ + toDOM( + _document?: Document | undefined, + hooks?: + | { + [x: string]: any; + } + | undefined, + binding?: any, + ): Node; + /** + * Inserts new content at an index. + * + * @example + * // Insert character 'a' at position 0 + * xml.insert(0, [new Y.XmlText('text')]) + * + * @param {number} index The index to insert content at + * @param {Array} content The array of content + */ + insert(index: number, content: Array): void; + /** + * Inserts new content at an index. + * + * @example + * // Insert character 'a' at position 0 + * xml.insert(0, [new Y.XmlText('text')]) + * + * @param {null|Item|YXmlElement|YXmlText} ref The index to insert content at + * @param {Array} content The array of content + */ + insertAfter( + ref: null | InternalStruct.Item | YXmlElement | YXmlText, + content: Array, + ): void; + /** + * Deletes elements starting from an index. + * + * @param {number} index Index at which to start deleting elements + * @param {number} [length=1] The number of elements to remove. Defaults to 1. + */ + delete(index: number, length?: number | undefined): void; + /** + * Transforms this YArray to a JavaScript Array. + * + * @return {Array} + */ + toArray(): Array; + /** + * Appends content to this YArray. + * + * @param {Array} content Array of content to append. + */ + push(content: Array): void; + /** + * Prepends content to this YArray. + * + * @param {Array} content Array of content to prepend. + */ + unshift(content: Array): void; + /** + * Returns the i-th element from a YArray. + * + * @param {number} index The index of the element to return from the YArray + * @return {YXmlElement|YXmlText} + */ + get(index: number): YXmlElement | YXmlText; + /** + * Returns a portion of this YXmlFragment into a JavaScript Array selected + * from start to end (end not included). + * + * @param {number} [start] + * @param {number} [end] + * @return {Array} + */ + slice( + start?: number | undefined, + end?: number | undefined, + ): Array; + /** + * Executes a provided function on once on every child element. + * + * @param {function(YXmlElement|YXmlText,number, typeof self):void} f A function to execute on every element of this YArray. + */ + forEach( + f: ( + arg0: YXmlElement | YXmlText, + arg1: number, + arg2: typeof self, + ) => void, + ): void; + } + + /** + * Define the elements to which a set of CSS queries apply. + * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors|CSS_Selectors} + */ + type CSS_Selector = string; + + /** + * An YXmlElement imitates the behavior of a + * 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]: ValueTypes }} [KV={ [key: string]: string }] + */ + export class YXmlElement< + KV extends { + [key: string]: ValueTypes; + } = { + [key: string]: string; + }, + > extends YXmlFragment { + constructor(nodeName?: string); + nodeName: string; + get nextSibling(): + | YXmlElement<{ + [key: string]: string; + }> + | YXmlText + | null; + get prevSibling(): + | YXmlElement<{ + [key: string]: string; + }> + | YXmlText + | null; + /** + * 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. + */ + clone(): YXmlElement; + /** + * Removes an attribute from this YXmlElement. + * + * @param {string} attributeName The attribute name that is to be removed. + * + * @public + */ + removeAttribute(attributeName: string): void; + /** + * Sets or updates an attribute. + * + * @template {keyof KV & string} KEY + * + * @param {KEY} attributeName The attribute name that is to be set. + * @param {KV[KEY]} attributeValue The attribute value that is to be set. + */ + setAttribute( + attributeName: KEY, + attributeValue: KV[KEY], + ): void; + /** + * Returns an attribute value that belongs to the attribute name. + * + * @template {keyof KV & string} KEY + * + * @param {KEY} attributeName The attribute name that identifies the + * queried value. + * @return {KV[KEY]|undefined} The queried attribute value. + */ + getAttribute( + attributeName: KEY_1, + ): KV[KEY_1] | undefined; + /** + * Returns whether an attribute exists + * + * @param {string} attributeName The attribute name to check for existence. + * @return {boolean} whether the attribute exists. + */ + hasAttribute(attributeName: string): boolean; + /** + * Returns all attribute name/value pairs in a JSON Object. + * + * @param {Snapshot} [snapshot] + * @return {{ [Key in Extract]?: KV[Key]}} A JSON Object that describes the attributes. + */ + getAttributes(snapshot?: Snapshot.Snapshot | undefined): { + [Key in Extract]?: KV[Key] | undefined; + }; + } + + type ValueTypes = + | Object + | number + | null + | Array + | string + | Uint8Array + | AbstractType; + + /** + * An Event that describes changes on a YXml Element or Yxml Fragment + */ + export class YXmlEvent extends YEvent< + | YXmlElement<{ + [key: string]: string; + }> + | YXmlFragment + | YXmlText + > { + /** + * @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: YXmlElement | YXmlText | YXmlFragment, + subs: Set, + transaction: Transaction, + ); + /** + * Set of all changed attributes. + * @type {Set} + */ + attributesChanged: Set; + } + + /** + * You can manage binding to a custom type with YXmlHook. + */ + export class YXmlHook extends YMap { + /** + * @param {string} hookName nodeName of the Dom Node. + */ + constructor(hookName: string); + hookName: string; + /** + * 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. + */ + clone(): YXmlHook; + /** + * 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} + */ + toDOM( + _document?: Document | undefined, + hooks?: + | { + [x: string]: any; + } + | undefined, + binding?: any, + ): Element; + } + + /** + * Represents text in a Dom Element. In the future this type will also handle + * simple formatting information like bold and italic. + */ + export class YXmlText extends YText { + get nextSibling(): + | YXmlElement<{ + [key: string]: string; + }> + | YXmlText + | null; + get prevSibling(): + | YXmlElement<{ + [key: string]: string; + }> + | YXmlText + | null; + /** + * 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. + */ + clone(): YXmlText; + /** + * Creates a Dom Element that mirrors this YXmlText. + * + * @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} + */ + toDOM( + _document?: Document | undefined, + hooks?: + | { + [x: string]: any; + } + | undefined, + binding?: any, + ): Text; + toString(): any; + } +} + +declare namespace InternalStruct { + export class AbstractStruct { + constructor(id: ID, length: number); + id: ID; + length: number; + get deleted(): boolean; + /** + * Merge this struct with the item to the right. + * 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 + */ + mergeWith(right: AbstractStruct): boolean; + /** + * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder The encoder to write data to. + * @param {number} offset + * @param {number} encodingRef + */ + write( + encoder: UpdateEncoderV1 | UpdateEncoderV2, + offset: number, + encodingRef: number, + ): void; + integrate(transaction: Transaction, offset: number): void; + } + + export class GC extends AbstractStruct { + delete(): void; + mergeWith(right: GC): boolean; + write(encoder: UpdateEncoderV1 | UpdateEncoderV2, offset: number): void; + getMissing(transaction: Transaction, store: StructStore): null | number; + } + + /** + * Abstract class that represents any content. + */ + export class Item extends AbstractStruct { + /** + * @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. + */ + constructor( + id: ID, + left: Item | null, + origin: ID | null, + right: Item | null, + rightOrigin: ID | null, + parent: AbstractType | ID | null, + parentSub: string | null, + content: AbstractContent, + ); + /** + * The item that was originally to the left of this item. + */ + origin: ID | null; + /** + * The item that is currently to the left of this item. + */ + left: Item | null; + /** + * The item that is currently to the right of this item. + */ + right: Item | null; + /** + * The item that was originally to the right of this item. + */ + rightOrigin: ID | null; + /** + * @type {AbstractType|ID|null} + */ + parent: AbstractType | ID | null; + /** + * If the parent refers to this item with some kind of key (e.g. YMap, the + * key is specified here. The key is then used to refer to the list in which + * to insert this item. If `parentSub = null` type._start is the list in + * which to insert to. Otherwise it is `parent._map`. + */ + parentSub: string | null; + /** + * If this type's effect is redone this type refers to the type that undid + * this operation. + */ + redone: ID | null; + content: AbstractContent; + /** + * bit1: keep + * bit2: countable + * bit3: deleted + * bit4: mark - mark node as fast-search-marker + * @type {number} byte + */ + info: number; + /** + * This is used to mark the item as an indexed fast-search marker + */ + set marker(arg: boolean); + get marker(): boolean; + set keep(arg: boolean); + /** + * If true, do not garbage collect this Item. + */ + get keep(): boolean; + get countable(): boolean; + set deleted(arg: boolean); + /** + * Whether this item was deleted or not. + */ + get deleted(): boolean; + markDeleted(): void; + /** + * Return the creator clientID of the missing op or define missing items and return null. + */ + getMissing(transaction: Transaction, store: StructStore): null | number; + /** + * Returns the next non-deleted item + */ + get next(): Item | null; + /** + * Returns the previous non-deleted item + */ + get prev(): Item | null; + /** + * Computes the last content address of this Item. + */ + get lastId(): ID; + /** + * Try to merge two items + */ + mergeWith(right: Item): boolean; + /** + * Mark this Item as deleted. + */ + delete(transaction: Transaction): void; + gc(store: StructStore, parentGCd: boolean): void; + /** + * Transform the properties of this type to binary and write it to an + * BinaryEncoder. + * + * This is called when this Item is sent to a remote peer. + * + * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder The encoder to write data to. + * @param {number} offset + */ + write(encoder: UpdateEncoderV1 | UpdateEncoderV2, offset: number): void; + } + + export class AbstractContent { + getLength(): number; + getContent(): Array; + /** + * Should return false if this Item is some kind of meta information + * (e.g. format information). + * + * * Whether this Item should be addressable via `yarray.get(i)` + * * Whether this Item should be counted when computing yarray.length + */ + isCountable(): boolean; + copy(): AbstractContent; + splice(_offset: number): AbstractContent; + mergeWith(_right: AbstractContent): boolean; + integrate(_transaction: Transaction, _item: Item): void; + delete(_transaction: Transaction): void; + gc(_store: StructStore): void; + write(_encoder: UpdateEncoderV1 | UpdateEncoderV2, _offset: number): void; + getRef(): number; + } + + export class Skip extends AbstractStruct { + delete(): void; + mergeWith(right: Skip): boolean; + write(encoder: UpdateEncoderV1 | UpdateEncoderV2, offset: number): void; + getMissing(transaction: Transaction, store: StructStore): null | number; + } +} diff --git a/yarn.lock b/yarn.lock index 2f97c54..9ffe11a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12,12 +12,178 @@ __metadata: languageName: node linkType: hard -"@cspotcode/source-map-support@npm:^0.8.0": - version: 0.8.1 - resolution: "@cspotcode/source-map-support@npm:0.8.1" +"@esbuild/aix-ppc64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/aix-ppc64@npm:0.21.5" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/android-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/android-arm64@npm:0.21.5" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/android-arm@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/android-arm@npm:0.21.5" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@esbuild/android-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/android-x64@npm:0.21.5" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/darwin-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/darwin-arm64@npm:0.21.5" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/darwin-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/darwin-x64@npm:0.21.5" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/freebsd-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/freebsd-arm64@npm:0.21.5" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/freebsd-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/freebsd-x64@npm:0.21.5" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/linux-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-arm64@npm:0.21.5" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/linux-arm@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-arm@npm:0.21.5" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@esbuild/linux-ia32@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-ia32@npm:0.21.5" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/linux-loong64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-loong64@npm:0.21.5" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + +"@esbuild/linux-mips64el@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-mips64el@npm:0.21.5" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + +"@esbuild/linux-ppc64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-ppc64@npm:0.21.5" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/linux-riscv64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-riscv64@npm:0.21.5" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + +"@esbuild/linux-s390x@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-s390x@npm:0.21.5" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + +"@esbuild/linux-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-x64@npm:0.21.5" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/netbsd-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/netbsd-x64@npm:0.21.5" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openbsd-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/openbsd-x64@npm:0.21.5" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/sunos-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/sunos-x64@npm:0.21.5" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/win32-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/win32-arm64@npm:0.21.5" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/win32-ia32@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/win32-ia32@npm:0.21.5" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/win32-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/win32-x64@npm:0.21.5" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@isaacs/cliui@npm:^8.0.2": + version: 8.0.2 + resolution: "@isaacs/cliui@npm:8.0.2" dependencies: - "@jridgewell/trace-mapping": 0.3.9 - checksum: 5718f267085ed8edb3e7ef210137241775e607ee18b77d95aa5bd7514f47f5019aa2d82d96b3bf342ef7aa890a346fa1044532ff7cc3009e7d24fce3ce6200fa + 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" + checksum: 4a473b9b32a7d4d3cfb7a614226e555091ff0c5a29a1734c28c72a182c2f6699b26fc6b5c2131dfd841e86b185aea714c72201d7c98c2fba5f17709333a67aeb languageName: node linkType: hard @@ -28,30 +194,20 @@ __metadata: languageName: node linkType: hard -"@jridgewell/resolve-uri@npm:^3.0.3, @jridgewell/resolve-uri@npm:^3.1.0": +"@jridgewell/resolve-uri@npm:^3.1.0": version: 3.1.1 resolution: "@jridgewell/resolve-uri@npm:3.1.1" checksum: f5b441fe7900eab4f9155b3b93f9800a916257f4e8563afbcd3b5a5337b55e52bd8ae6735453b1b745457d9f6cdb16d74cd6220bbdd98cf153239e13f6cbb653 languageName: node linkType: hard -"@jridgewell/sourcemap-codec@npm:^1.4.10, @jridgewell/sourcemap-codec@npm:^1.4.14": +"@jridgewell/sourcemap-codec@npm:^1.4.14": version: 1.4.15 resolution: "@jridgewell/sourcemap-codec@npm:1.4.15" checksum: b881c7e503db3fc7f3c1f35a1dd2655a188cc51a3612d76efc8a6eb74728bef5606e6758ee77423e564092b4a518aba569bbb21c9bac5ab7a35b0c6ae7e344c8 languageName: node linkType: hard -"@jridgewell/trace-mapping@npm:0.3.9": - version: 0.3.9 - resolution: "@jridgewell/trace-mapping@npm:0.3.9" - dependencies: - "@jridgewell/resolve-uri": ^3.0.3 - "@jridgewell/sourcemap-codec": ^1.4.10 - checksum: d89597752fd88d3f3480845691a05a44bd21faac18e2185b6f436c3b0fd0c5a859fbbd9aaa92050c4052caf325ad3e10e2e1d1b64327517471b7d51babc0ddef - languageName: node - linkType: hard - "@jridgewell/trace-mapping@npm:^0.3.12": version: 0.3.19 resolution: "@jridgewell/trace-mapping@npm:0.3.19" @@ -78,40 +234,41 @@ __metadata: languageName: node linkType: hard -"@taplo/cli@npm:^0.5.2": - version: 0.5.2 - resolution: "@taplo/cli@npm:0.5.2" - bin: - taplo: dist/cli.js - checksum: c2e0e584172bfee1cca6624bdb4470259179e232472fc7f4bbbd2e0127233039b9ace21a8d6b8d5081b157d9f046dc942ab27a634e23924b8c8a6096f1d04e27 - languageName: node - linkType: hard - -"@tsconfig/node10@npm:^1.0.7": - version: 1.0.9 - resolution: "@tsconfig/node10@npm:1.0.9" - checksum: a33ae4dc2a621c0678ac8ac4bceb8e512ae75dac65417a2ad9b022d9b5411e863c4c198b6ba9ef659e14b9fb609bbec680841a2e84c1172df7a5ffcf076539df +"@npmcli/agent@npm:^2.0.0": + version: 2.2.2 + resolution: "@npmcli/agent@npm:2.2.2" + dependencies: + agent-base: ^7.1.0 + http-proxy-agent: ^7.0.0 + https-proxy-agent: ^7.0.1 + lru-cache: ^10.0.1 + socks-proxy-agent: ^8.0.3 + checksum: 67de7b88cc627a79743c88bab35e023e23daf13831a8aa4e15f998b92f5507b644d8ffc3788afc8e64423c612e0785a6a92b74782ce368f49a6746084b50d874 languageName: node linkType: hard -"@tsconfig/node12@npm:^1.0.7": - version: 1.0.11 - resolution: "@tsconfig/node12@npm:1.0.11" - checksum: 5ce29a41b13e7897a58b8e2df11269c5395999e588b9a467386f99d1d26f6c77d1af2719e407621412520ea30517d718d5192a32403b8dfcc163bf33e40a338a +"@npmcli/fs@npm:^3.1.0": + version: 3.1.1 + resolution: "@npmcli/fs@npm:3.1.1" + dependencies: + semver: ^7.3.5 + checksum: d960cab4b93adcb31ce223bfb75c5714edbd55747342efb67dcc2f25e023d930a7af6ece3e75f2f459b6f38fc14d031c766f116cd124fdc937fd33112579e820 languageName: node linkType: hard -"@tsconfig/node14@npm:^1.0.0": - version: 1.0.3 - resolution: "@tsconfig/node14@npm:1.0.3" - checksum: 19275fe80c4c8d0ad0abed6a96dbf00642e88b220b090418609c4376e1cef81bf16237bf170ad1b341452feddb8115d8dd2e5acdfdea1b27422071163dc9ba9d +"@pkgjs/parseargs@npm:^0.11.0": + version: 0.11.0 + resolution: "@pkgjs/parseargs@npm:0.11.0" + checksum: 6ad6a00fc4f2f2cfc6bff76fb1d88b8ee20bc0601e18ebb01b6d4be583733a860239a521a7fbca73b612e66705078809483549d2b18f370eb346c5155c8e4a0f languageName: node linkType: hard -"@tsconfig/node16@npm:^1.0.2": - version: 1.0.4 - resolution: "@tsconfig/node16@npm:1.0.4" - checksum: 202319785901f942a6e1e476b872d421baec20cf09f4b266a1854060efbf78cde16a4d256e8bc949d31e6cd9a90f1e8ef8fb06af96a65e98338a2b6b0de0a0ff +"@taplo/cli@npm:^0.5.2": + version: 0.5.2 + resolution: "@taplo/cli@npm:0.5.2" + bin: + taplo: dist/cli.js + checksum: c2e0e584172bfee1cca6624bdb4470259179e232472fc7f4bbbd2e0127233039b9ace21a8d6b8d5081b157d9f046dc942ab27a634e23924b8c8a6096f1d04e27 languageName: node linkType: hard @@ -167,25 +324,35 @@ __metadata: "@types/prompts": ^2.4.4 c8: ^8.0.1 prompts: ^2.4.2 - ts-node: ^10.9.2 + tsx: ^4.16.2 typescript: ^5.1.6 - yjs: ^13.6.8 + yjs: ^13.6.18 languageName: unknown linkType: soft -"acorn-walk@npm:^8.1.1": - version: 8.2.0 - resolution: "acorn-walk@npm:8.2.0" - checksum: 1715e76c01dd7b2d4ca472f9c58968516a4899378a63ad5b6c2d668bba8da21a71976c14ec5f5b75f887b6317c4ae0b897ab141c831d741dc76024d8745f1ad1 +"abbrev@npm:^2.0.0": + version: 2.0.0 + resolution: "abbrev@npm:2.0.0" + checksum: 0e994ad2aa6575f94670d8a2149afe94465de9cedaaaac364e7fb43a40c3691c980ff74899f682f4ca58fa96b4cbd7421a015d3a6defe43a442117d7821a2f36 languageName: node linkType: hard -"acorn@npm:^8.4.1": - version: 8.10.0 - resolution: "acorn@npm:8.10.0" - bin: - acorn: bin/acorn - checksum: 538ba38af0cc9e5ef983aee196c4b8b4d87c0c94532334fa7e065b2c8a1f85863467bb774231aae91613fcda5e68740c15d97b1967ae3394d20faddddd8af61d +"agent-base@npm:^7.0.2, agent-base@npm:^7.1.0, agent-base@npm:^7.1.1": + version: 7.1.1 + resolution: "agent-base@npm:7.1.1" + dependencies: + debug: ^4.3.4 + checksum: 51c158769c5c051482f9ca2e6e1ec085ac72b5a418a9b31b4e82fe6c0a6699adb94c1c42d246699a587b3335215037091c79e0de512c516f73b6ea844202f037 + languageName: node + linkType: hard + +"aggregate-error@npm:^3.0.0": + version: 3.1.0 + resolution: "aggregate-error@npm:3.1.0" + dependencies: + clean-stack: ^2.0.0 + indent-string: ^4.0.0 + checksum: 1101a33f21baa27a2fa8e04b698271e64616b886795fd43c31068c07533c7b3facfcaf4e9e0cab3624bd88f729a592f1c901a1a229c9e490eafce411a8644b79 languageName: node linkType: hard @@ -237,13 +404,6 @@ __metadata: languageName: node linkType: hard -"arg@npm:^4.1.0": - version: 4.1.3 - resolution: "arg@npm:4.1.3" - checksum: 544af8dd3f60546d3e4aff084d451b96961d2267d668670199692f8d054f0415d86fc5497d0e641e91546f0aa920e7c29e5250e99fc89f5552a34b5d93b77f43 - languageName: node - linkType: hard - "balanced-match@npm:^1.0.0": version: 1.0.2 resolution: "balanced-match@npm:1.0.2" @@ -261,6 +421,15 @@ __metadata: languageName: node linkType: hard +"brace-expansion@npm:^2.0.1": + version: 2.0.1 + resolution: "brace-expansion@npm:2.0.1" + dependencies: + balanced-match: ^1.0.0 + checksum: a61e7cd2e8a8505e9f0036b3b6108ba5e926b4b55089eeb5550cd04a471fe216c96d4fe7e4c7f995c728c554ae20ddfc4244cad10aef255e72b62930afd233d1 + languageName: node + linkType: hard + "braces@npm:^3.0.2": version: 3.0.2 resolution: "braces@npm:3.0.2" @@ -292,6 +461,26 @@ __metadata: languageName: node linkType: hard +"cacache@npm:^18.0.0": + version: 18.0.3 + resolution: "cacache@npm:18.0.3" + dependencies: + "@npmcli/fs": ^3.1.0 + fs-minipass: ^3.0.0 + glob: ^10.2.2 + lru-cache: ^10.0.1 + minipass: ^7.0.3 + minipass-collect: ^2.0.1 + minipass-flush: ^1.0.5 + minipass-pipeline: ^1.2.4 + p-map: ^4.0.0 + ssri: ^10.0.0 + tar: ^6.1.11 + unique-filename: ^3.0.0 + checksum: b717fd9b36e9c3279bfde4545c3a8f6d5a539b084ee26a9504d48f83694beb724057d26e090b97540f9cc62bea18b9f6cf671c50e18fb7dac60eda9db691714f + languageName: node + linkType: hard + "chalk@npm:5.3.0": version: 5.3.0 resolution: "chalk@npm:5.3.0" @@ -310,6 +499,20 @@ __metadata: languageName: node linkType: hard +"chownr@npm:^2.0.0": + version: 2.0.0 + resolution: "chownr@npm:2.0.0" + checksum: c57cf9dd0791e2f18a5ee9c1a299ae6e801ff58fee96dc8bfd0dcb4738a6ce58dd252a3605b1c93c6418fe4f9d5093b28ffbf4d66648cb2a9c67eaef9679be2f + languageName: node + linkType: hard + +"clean-stack@npm:^2.0.0": + version: 2.2.0 + resolution: "clean-stack@npm:2.2.0" + checksum: 2ac8cd2b2f5ec986a3c743935ec85b07bc174d5421a5efc8017e1f146a1cf5f781ae962618f416352103b32c9cd7e203276e8c28241bbe946160cab16149fb68 + languageName: node + linkType: hard + "cli-cursor@npm:^4.0.0": version: 4.0.0 resolution: "cli-cursor@npm:4.0.0" @@ -400,13 +603,6 @@ __metadata: languageName: node linkType: hard -"create-require@npm:^1.1.0": - version: 1.1.1 - resolution: "create-require@npm:1.1.1" - checksum: a9a1503d4390d8b59ad86f4607de7870b39cad43d929813599a23714831e81c520bddf61bcdd1f8e30f05fd3a2b71ae8538e946eb2786dc65c2bbc520f692eff - languageName: node - linkType: hard - "cross-spawn@npm:^6.0.5": version: 6.0.5 resolution: "cross-spawn@npm:6.0.5" @@ -431,6 +627,18 @@ __metadata: languageName: node linkType: hard +"debug@npm:4, debug@npm:^4.3.4": + version: 4.3.5 + resolution: "debug@npm:4.3.5" + dependencies: + ms: 2.1.2 + peerDependenciesMeta: + supports-color: + optional: true + checksum: 7c002b51e256257f936dda09eb37167df952758c57badf6bf44bdc40b89a4bcb8e5a0a2e4c7b53f97c69e2970dd5272d33a757378a12c8f8e64ea7bf99e8e86e + languageName: node + linkType: hard + "debug@npm:4.3.4": version: 4.3.4 resolution: "debug@npm:4.3.4" @@ -443,13 +651,6 @@ __metadata: languageName: node linkType: hard -"diff@npm:^4.0.1": - version: 4.0.2 - resolution: "diff@npm:4.0.2" - checksum: f2c09b0ce4e6b301c221addd83bf3f454c0bc00caa3dd837cf6c127d6edf7223aa2bbe3b688feea110b7f262adbfc845b757c44c8a9f8c0c5b15d8fa9ce9d20d - languageName: node - linkType: hard - "eastasianwidth@npm:^0.2.0": version: 0.2.0 resolution: "eastasianwidth@npm:0.2.0" @@ -471,6 +672,29 @@ __metadata: languageName: node linkType: hard +"encoding@npm:^0.1.13": + version: 0.1.13 + resolution: "encoding@npm:0.1.13" + dependencies: + iconv-lite: ^0.6.2 + checksum: bb98632f8ffa823996e508ce6a58ffcf5856330fde839ae42c9e1f436cc3b5cc651d4aeae72222916545428e54fd0f6aa8862fd8d25bdbcc4589f1e3f3715e7f + languageName: node + linkType: hard + +"env-paths@npm:^2.2.0": + version: 2.2.1 + resolution: "env-paths@npm:2.2.1" + checksum: 65b5df55a8bab92229ab2b40dad3b387fad24613263d103a97f91c9fe43ceb21965cd3392b1ccb5d77088021e525c4e0481adb309625d0cb94ade1d1fb8dc17e + languageName: node + linkType: hard + +"err-code@npm:^2.0.2": + version: 2.0.3 + resolution: "err-code@npm:2.0.3" + checksum: 8b7b1be20d2de12d2255c0bc2ca638b7af5171142693299416e6a9339bd7d88fc8d7707d913d78e0993176005405a236b066b45666b27b797252c771156ace54 + languageName: node + linkType: hard + "error-ex@npm:^1.3.1": version: 1.3.2 resolution: "error-ex@npm:1.3.2" @@ -480,6 +704,86 @@ __metadata: languageName: node linkType: hard +"esbuild@npm:~0.21.5": + version: 0.21.5 + resolution: "esbuild@npm:0.21.5" + dependencies: + "@esbuild/aix-ppc64": 0.21.5 + "@esbuild/android-arm": 0.21.5 + "@esbuild/android-arm64": 0.21.5 + "@esbuild/android-x64": 0.21.5 + "@esbuild/darwin-arm64": 0.21.5 + "@esbuild/darwin-x64": 0.21.5 + "@esbuild/freebsd-arm64": 0.21.5 + "@esbuild/freebsd-x64": 0.21.5 + "@esbuild/linux-arm": 0.21.5 + "@esbuild/linux-arm64": 0.21.5 + "@esbuild/linux-ia32": 0.21.5 + "@esbuild/linux-loong64": 0.21.5 + "@esbuild/linux-mips64el": 0.21.5 + "@esbuild/linux-ppc64": 0.21.5 + "@esbuild/linux-riscv64": 0.21.5 + "@esbuild/linux-s390x": 0.21.5 + "@esbuild/linux-x64": 0.21.5 + "@esbuild/netbsd-x64": 0.21.5 + "@esbuild/openbsd-x64": 0.21.5 + "@esbuild/sunos-x64": 0.21.5 + "@esbuild/win32-arm64": 0.21.5 + "@esbuild/win32-ia32": 0.21.5 + "@esbuild/win32-x64": 0.21.5 + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 2911c7b50b23a9df59a7d6d4cdd3a4f85855787f374dce751148dbb13305e0ce7e880dde1608c2ab7a927fc6cec3587b80995f7fc87a64b455f8b70b55fd8ec1 + languageName: node + linkType: hard + "escalade@npm:^3.1.1": version: 3.1.1 resolution: "escalade@npm:3.1.1" @@ -518,6 +822,13 @@ __metadata: languageName: node linkType: hard +"exponential-backoff@npm:^3.1.1": + version: 3.1.1 + resolution: "exponential-backoff@npm:3.1.1" + checksum: 3d21519a4f8207c99f7457287291316306255a328770d320b401114ec8481986e4e467e854cb9914dd965e0a1ca810a23ccb559c642c88f4c7f55c55778a9b48 + languageName: node + linkType: hard + "fill-range@npm:^7.0.1": version: 7.0.1 resolution: "fill-range@npm:7.0.1" @@ -547,6 +858,34 @@ __metadata: languageName: node linkType: hard +"foreground-child@npm:^3.1.0": + version: 3.2.1 + resolution: "foreground-child@npm:3.2.1" + dependencies: + cross-spawn: ^7.0.0 + signal-exit: ^4.0.1 + checksum: 3e2e844d6003c96d70affe8ae98d7eaaba269a868c14d997620c088340a8775cd5d2d9043e6ceebae1928d8d9a874911c4d664b9a267e8995945df20337aebc0 + languageName: node + linkType: hard + +"fs-minipass@npm:^2.0.0": + version: 2.1.0 + resolution: "fs-minipass@npm:2.1.0" + dependencies: + minipass: ^3.0.0 + checksum: 1b8d128dae2ac6cc94230cc5ead341ba3e0efaef82dab46a33d171c044caaa6ca001364178d42069b2809c35a1c3c35079a32107c770e9ffab3901b59af8c8b1 + languageName: node + linkType: hard + +"fs-minipass@npm:^3.0.0": + version: 3.0.3 + resolution: "fs-minipass@npm:3.0.3" + dependencies: + minipass: ^7.0.3 + checksum: 8722a41109130851d979222d3ec88aabaceeaaf8f57b2a8f744ef8bd2d1ce95453b04a61daa0078822bc5cd21e008814f06fe6586f56fef511e71b8d2394d802 + languageName: node + linkType: hard + "fs.realpath@npm:^1.0.0": version: 1.0.0 resolution: "fs.realpath@npm:1.0.0" @@ -554,6 +893,25 @@ __metadata: languageName: node linkType: hard +"fsevents@npm:~2.3.3": + version: 2.3.3 + resolution: "fsevents@npm:2.3.3" + dependencies: + node-gyp: latest + checksum: 11e6ea6fea15e42461fc55b4b0e4a0a3c654faa567f1877dbd353f39156f69def97a69936d1746619d656c4b93de2238bf731f6085a03a50cabf287c9d024317 + conditions: os=darwin + languageName: node + linkType: hard + +"fsevents@patch:fsevents@~2.3.3#~builtin": + version: 2.3.3 + resolution: "fsevents@patch:fsevents@npm%3A2.3.3#~builtin::version=2.3.3&hash=df0bf1" + dependencies: + node-gyp: latest + conditions: os=darwin + languageName: node + linkType: hard + "get-caller-file@npm:^2.0.5": version: 2.0.5 resolution: "get-caller-file@npm:2.0.5" @@ -568,6 +926,31 @@ __metadata: languageName: node linkType: hard +"get-tsconfig@npm:^4.7.5": + version: 4.7.5 + resolution: "get-tsconfig@npm:4.7.5" + dependencies: + resolve-pkg-maps: ^1.0.0 + checksum: e5b271fae2b4cd1869bbfc58db56983026cc4a08fdba988725a6edd55d04101507de154722503a22ee35920898ff9bdcba71f99d93b17df35dddb8e8a2ad91be + languageName: node + linkType: hard + +"glob@npm:^10.2.2, glob@npm:^10.3.10": + version: 10.4.2 + resolution: "glob@npm:10.4.2" + dependencies: + foreground-child: ^3.1.0 + jackspeak: ^3.1.2 + minimatch: ^9.0.4 + minipass: ^7.1.2 + package-json-from-dist: ^1.0.0 + path-scurry: ^1.11.1 + bin: + glob: dist/esm/bin.mjs + checksum: bd7c0e30701136e936f414e5f6f82c7f04503f01df77408f177aa584927412f0bde0338e6ec541618cd21eacc57dde33e7b3c6c0a779cc1c6e6a0e14f3d15d9b + languageName: node + linkType: hard + "glob@npm:^7.1.3, glob@npm:^7.1.4": version: 7.2.3 resolution: "glob@npm:7.2.3" @@ -582,7 +965,7 @@ __metadata: languageName: node linkType: hard -"graceful-fs@npm:^4.1.2": +"graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.2.6": version: 4.2.11 resolution: "graceful-fs@npm:4.2.11" checksum: ac85f94da92d8eb6b7f5a8b20ce65e43d66761c55ce85ac96df6865308390da45a8d3f0296dd3a663de65d30ba497bd46c696cc1e248c72b13d6d567138a4fc7 @@ -626,6 +1009,33 @@ __metadata: languageName: node linkType: hard +"http-cache-semantics@npm:^4.1.1": + version: 4.1.1 + resolution: "http-cache-semantics@npm:4.1.1" + checksum: 83ac0bc60b17a3a36f9953e7be55e5c8f41acc61b22583060e8dedc9dd5e3607c823a88d0926f9150e571f90946835c7fe150732801010845c72cd8bbff1a236 + languageName: node + linkType: hard + +"http-proxy-agent@npm:^7.0.0": + version: 7.0.2 + resolution: "http-proxy-agent@npm:7.0.2" + dependencies: + agent-base: ^7.1.0 + debug: ^4.3.4 + checksum: 670858c8f8f3146db5889e1fa117630910101db601fff7d5a8aa637da0abedf68c899f03d3451cac2f83bcc4c3d2dabf339b3aa00ff8080571cceb02c3ce02f3 + languageName: node + linkType: hard + +"https-proxy-agent@npm:^7.0.1": + version: 7.0.5 + resolution: "https-proxy-agent@npm:7.0.5" + dependencies: + agent-base: ^7.0.2 + debug: 4 + checksum: 2e1a28960f13b041a50702ee74f240add8e75146a5c37fc98f1960f0496710f6918b3a9fe1e5aba41e50f58e6df48d107edd9c405c5f0d73ac260dabf2210857 + languageName: node + linkType: hard + "human-signals@npm:^4.3.0": version: 4.3.1 resolution: "human-signals@npm:4.3.1" @@ -642,6 +1052,29 @@ __metadata: languageName: node linkType: hard +"iconv-lite@npm:^0.6.2": + version: 0.6.3 + resolution: "iconv-lite@npm:0.6.3" + dependencies: + safer-buffer: ">= 2.1.2 < 3.0.0" + checksum: 3f60d47a5c8fc3313317edfd29a00a692cc87a19cac0159e2ce711d0ebc9019064108323b5e493625e25594f11c6236647d8e256fbe7a58f4a3b33b89e6d30bf + languageName: node + linkType: hard + +"imurmurhash@npm:^0.1.4": + version: 0.1.4 + resolution: "imurmurhash@npm:0.1.4" + checksum: 7cae75c8cd9a50f57dadd77482359f659eaebac0319dd9368bcd1714f55e65badd6929ca58569da2b6494ef13fdd5598cd700b1eba23f8b79c5f19d195a3ecf7 + languageName: node + linkType: hard + +"indent-string@npm:^4.0.0": + version: 4.0.0 + resolution: "indent-string@npm:4.0.0" + checksum: 824cfb9929d031dabf059bebfe08cf3137365e112019086ed3dcff6a0a7b698cb80cf67ccccde0e25b9e2d7527aa6cc1fed1ac490c752162496caba3e6699612 + languageName: node + linkType: hard + "inflight@npm:^1.0.4": version: 1.0.6 resolution: "inflight@npm:1.0.6" @@ -659,6 +1092,16 @@ __metadata: languageName: node linkType: hard +"ip-address@npm:^9.0.5": + version: 9.0.5 + resolution: "ip-address@npm:9.0.5" + dependencies: + jsbn: 1.1.0 + sprintf-js: ^1.1.3 + checksum: aa15f12cfd0ef5e38349744e3654bae649a34c3b10c77a674a167e99925d1549486c5b14730eebce9fea26f6db9d5e42097b00aa4f9f612e68c79121c71652dc + languageName: node + linkType: hard + "is-arrayish@npm:^0.2.1": version: 0.2.1 resolution: "is-arrayish@npm:0.2.1" @@ -689,6 +1132,13 @@ __metadata: languageName: node linkType: hard +"is-lambda@npm:^1.0.1": + version: 1.0.1 + resolution: "is-lambda@npm:1.0.1" + checksum: 93a32f01940220532e5948538699ad610d5924ac86093fcee83022252b363eb0cc99ba53ab084a04e4fb62bf7b5731f55496257a4c38adf87af9c4d352c71c35 + languageName: node + linkType: hard + "is-number@npm:^7.0.0": version: 7.0.0 resolution: "is-number@npm:7.0.0" @@ -710,6 +1160,13 @@ __metadata: languageName: node linkType: hard +"isexe@npm:^3.1.1": + version: 3.1.1 + resolution: "isexe@npm:3.1.1" + checksum: 7fe1931ee4e88eb5aa524cd3ceb8c882537bc3a81b02e438b240e47012eef49c86904d0f0e593ea7c3a9996d18d0f1f3be8d3eaa92333977b0c3a9d353d5563e + languageName: node + linkType: hard + "isomorphic.js@npm:^0.2.4": version: 0.2.5 resolution: "isomorphic.js@npm:0.2.5" @@ -745,6 +1202,26 @@ __metadata: languageName: node linkType: hard +"jackspeak@npm:^3.1.2": + version: 3.4.0 + resolution: "jackspeak@npm:3.4.0" + dependencies: + "@isaacs/cliui": ^8.0.2 + "@pkgjs/parseargs": ^0.11.0 + dependenciesMeta: + "@pkgjs/parseargs": + optional: true + checksum: 350f6f311018bb175ffbe736b19c26ac0b134bb5a17a638169e89594eb0c24ab1c658ab3a2fda24ff63b3b19292e1a5ec19d2255bc526df704e8168d392bef85 + languageName: node + linkType: hard + +"jsbn@npm:1.1.0": + version: 1.1.0 + resolution: "jsbn@npm:1.1.0" + checksum: 944f924f2bd67ad533b3850eee47603eed0f6ae425fd1ee8c760f477e8c34a05f144c1bd4f5a5dd1963141dc79a2c55f89ccc5ab77d039e7077f3ad196b64965 + languageName: node + linkType: hard + "json-parse-better-errors@npm:^1.0.1": version: 1.0.2 resolution: "json-parse-better-errors@npm:1.0.2" @@ -759,15 +1236,16 @@ __metadata: languageName: node linkType: hard -"lib0@npm:^0.2.74": - version: 0.2.85 - resolution: "lib0@npm:0.2.85" +"lib0@npm:^0.2.86": + version: 0.2.94 + resolution: "lib0@npm:0.2.94" dependencies: isomorphic.js: ^0.2.4 bin: + 0ecdsa-generate-keypair: bin/0ecdsa-generate-keypair.js 0gentesthtml: bin/gentesthtml.js 0serve: bin/0serve.js - checksum: 6a3c5c5c3f37f0940eff9309b2595f9a77822f521773db773e0d809309ccf5e6ecab8f39cc47b55b6b167f60b3824c44bb7d92b5c9ffb81a3f331b21277906d2 + checksum: b091c7b39875a58d92ae6dcb014a42b56caf1336e03d310403c47a2fcea079eba92ffb43da90eaff9d010f08bcd20de35fffed7c655c83312ff4e3477f5dc261 languageName: node linkType: hard @@ -851,12 +1329,10 @@ __metadata: languageName: node linkType: hard -"lru-cache@npm:^6.0.0": - version: 6.0.0 - resolution: "lru-cache@npm:6.0.0" - dependencies: - yallist: ^4.0.0 - checksum: f97f499f898f23e4585742138a22f22526254fdba6d75d41a1c2526b3b6cc5747ef59c5612ba7375f42aca4f8461950e925ba08c991ead0651b4918b7c978297 +"lru-cache@npm:^10.0.1, lru-cache@npm:^10.2.0": + version: 10.3.0 + resolution: "lru-cache@npm:10.3.0" + checksum: f2289639bd94cf3c87bfd8a77ac991f9afe3af004ddca3548c3dae63ead1c73bba449a60a4e270992e16cf3261b3d4130943234d52ca3a4d4de2fc074a3cc7b5 languageName: node linkType: hard @@ -869,10 +1345,23 @@ __metadata: languageName: node linkType: hard -"make-error@npm:^1.1.1": - version: 1.3.6 - resolution: "make-error@npm:1.3.6" - checksum: b86e5e0e25f7f777b77fabd8e2cbf15737972869d852a22b7e73c17623928fccb826d8e46b9951501d3f20e51ad74ba8c59ed584f610526a48f8ccf88aaec402 +"make-fetch-happen@npm:^13.0.0": + version: 13.0.1 + resolution: "make-fetch-happen@npm:13.0.1" + dependencies: + "@npmcli/agent": ^2.0.0 + cacache: ^18.0.0 + http-cache-semantics: ^4.1.1 + is-lambda: ^1.0.1 + minipass: ^7.0.2 + minipass-fetch: ^3.0.0 + minipass-flush: ^1.0.5 + minipass-pipeline: ^1.2.4 + negotiator: ^0.6.3 + proc-log: ^4.2.0 + promise-retry: ^2.0.1 + ssri: ^10.0.0 + checksum: 5c9fad695579b79488fa100da05777213dd9365222f85e4757630f8dd2a21a79ddd3206c78cfd6f9b37346819681782b67900ac847a57cf04190f52dda5343fd languageName: node linkType: hard @@ -923,6 +1412,108 @@ __metadata: languageName: node linkType: hard +"minimatch@npm:^9.0.4": + version: 9.0.5 + resolution: "minimatch@npm:9.0.5" + dependencies: + brace-expansion: ^2.0.1 + checksum: 2c035575eda1e50623c731ec6c14f65a85296268f749b9337005210bb2b34e2705f8ef1a358b188f69892286ab99dc42c8fb98a57bde55c8d81b3023c19cea28 + languageName: node + linkType: hard + +"minipass-collect@npm:^2.0.1": + version: 2.0.1 + resolution: "minipass-collect@npm:2.0.1" + dependencies: + minipass: ^7.0.3 + checksum: b251bceea62090f67a6cced7a446a36f4cd61ee2d5cea9aee7fff79ba8030e416327a1c5aa2908dc22629d06214b46d88fdab8c51ac76bacbf5703851b5ad342 + languageName: node + linkType: hard + +"minipass-fetch@npm:^3.0.0": + version: 3.0.5 + resolution: "minipass-fetch@npm:3.0.5" + dependencies: + encoding: ^0.1.13 + minipass: ^7.0.3 + minipass-sized: ^1.0.3 + minizlib: ^2.1.2 + dependenciesMeta: + encoding: + optional: true + checksum: 8047d273236157aab27ab7cd8eab7ea79e6ecd63e8f80c3366ec076cb9a0fed550a6935bab51764369027c414647fd8256c2a20c5445fb250c483de43350de83 + languageName: node + linkType: hard + +"minipass-flush@npm:^1.0.5": + version: 1.0.5 + resolution: "minipass-flush@npm:1.0.5" + dependencies: + minipass: ^3.0.0 + checksum: 56269a0b22bad756a08a94b1ffc36b7c9c5de0735a4dd1ab2b06c066d795cfd1f0ac44a0fcae13eece5589b908ecddc867f04c745c7009be0b566421ea0944cf + languageName: node + linkType: hard + +"minipass-pipeline@npm:^1.2.4": + version: 1.2.4 + resolution: "minipass-pipeline@npm:1.2.4" + dependencies: + minipass: ^3.0.0 + checksum: b14240dac0d29823c3d5911c286069e36d0b81173d7bdf07a7e4a91ecdef92cdff4baaf31ea3746f1c61e0957f652e641223970870e2353593f382112257971b + languageName: node + linkType: hard + +"minipass-sized@npm:^1.0.3": + version: 1.0.3 + resolution: "minipass-sized@npm:1.0.3" + dependencies: + minipass: ^3.0.0 + checksum: 79076749fcacf21b5d16dd596d32c3b6bf4d6e62abb43868fac21674078505c8b15eaca4e47ed844985a4514854f917d78f588fcd029693709417d8f98b2bd60 + languageName: node + linkType: hard + +"minipass@npm:^3.0.0": + version: 3.3.6 + resolution: "minipass@npm:3.3.6" + dependencies: + yallist: ^4.0.0 + checksum: a30d083c8054cee83cdcdc97f97e4641a3f58ae743970457b1489ce38ee1167b3aaf7d815cd39ec7a99b9c40397fd4f686e83750e73e652b21cb516f6d845e48 + languageName: node + linkType: hard + +"minipass@npm:^5.0.0": + version: 5.0.0 + resolution: "minipass@npm:5.0.0" + checksum: 425dab288738853fded43da3314a0b5c035844d6f3097a8e3b5b29b328da8f3c1af6fc70618b32c29ff906284cf6406b6841376f21caaadd0793c1d5a6a620ea + languageName: node + linkType: hard + +"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.1.2": + version: 7.1.2 + resolution: "minipass@npm:7.1.2" + checksum: 2bfd325b95c555f2b4d2814d49325691c7bee937d753814861b0b49d5edcda55cbbf22b6b6a60bb91eddac8668771f03c5ff647dcd9d0f798e9548b9cdc46ee3 + languageName: node + linkType: hard + +"minizlib@npm:^2.1.1, minizlib@npm:^2.1.2": + version: 2.1.2 + resolution: "minizlib@npm:2.1.2" + dependencies: + minipass: ^3.0.0 + yallist: ^4.0.0 + checksum: f1fdeac0b07cf8f30fcf12f4b586795b97be856edea22b5e9072707be51fc95d41487faec3f265b42973a304fe3a64acd91a44a3826a963e37b37bafde0212c3 + languageName: node + linkType: hard + +"mkdirp@npm:^1.0.3": + version: 1.0.4 + resolution: "mkdirp@npm:1.0.4" + bin: + mkdirp: bin/cmd.js + checksum: a96865108c6c3b1b8e1d5e9f11843de1e077e57737602de1b82030815f311be11f96f09cce59bd5b903d0b29834733e5313f9301e3ed6d6f6fba2eae0df4298f + languageName: node + linkType: hard + "ms@npm:2.1.2": version: 2.1.2 resolution: "ms@npm:2.1.2" @@ -930,6 +1521,13 @@ __metadata: languageName: node linkType: hard +"negotiator@npm:^0.6.3": + version: 0.6.3 + resolution: "negotiator@npm:0.6.3" + checksum: b8ffeb1e262eff7968fc90a2b6767b04cfd9842582a9d0ece0af7049537266e7b2506dfb1d107a32f06dd849ab2aea834d5830f7f4d0e5cb7d36e1ae55d021d9 + languageName: node + linkType: hard + "nice-try@npm:^1.0.4": version: 1.0.5 resolution: "nice-try@npm:1.0.5" @@ -937,6 +1535,37 @@ __metadata: languageName: node linkType: hard +"node-gyp@npm:latest": + version: 10.1.0 + resolution: "node-gyp@npm:10.1.0" + dependencies: + env-paths: ^2.2.0 + exponential-backoff: ^3.1.1 + glob: ^10.3.10 + graceful-fs: ^4.2.6 + make-fetch-happen: ^13.0.0 + nopt: ^7.0.0 + proc-log: ^3.0.0 + semver: ^7.3.5 + tar: ^6.1.2 + which: ^4.0.0 + bin: + node-gyp: bin/node-gyp.js + checksum: 72e2ab4b23fc32007a763da94018f58069fc0694bf36115d49a2b195c8831e12cf5dd1e7a3718fa85c06969aedf8fc126722d3b672ec1cb27e06ed33caee3c60 + languageName: node + linkType: hard + +"nopt@npm:^7.0.0": + version: 7.2.1 + resolution: "nopt@npm:7.2.1" + dependencies: + abbrev: ^2.0.0 + bin: + nopt: bin/nopt.js + checksum: 6fa729cc77ce4162cfad8abbc9ba31d4a0ff6850c3af61d59b505653bef4781ec059f8890ecfe93ee8aa0c511093369cca88bfc998101616a2904e715bbbb7c9 + languageName: node + linkType: hard + "normalize-package-data@npm:^2.3.2": version: 2.5.0 resolution: "normalize-package-data@npm:2.5.0" @@ -1024,6 +1653,22 @@ __metadata: languageName: node linkType: hard +"p-map@npm:^4.0.0": + version: 4.0.0 + resolution: "p-map@npm:4.0.0" + dependencies: + aggregate-error: ^3.0.0 + checksum: cb0ab21ec0f32ddffd31dfc250e3afa61e103ef43d957cc45497afe37513634589316de4eb88abdfd969fe6410c22c0b93ab24328833b8eb1ccc087fc0442a1c + languageName: node + linkType: hard + +"package-json-from-dist@npm:^1.0.0": + version: 1.0.0 + resolution: "package-json-from-dist@npm:1.0.0" + checksum: ac706ec856a5a03f5261e4e48fa974f24feb044d51f84f8332e2af0af04fbdbdd5bbbfb9cbbe354190409bc8307c83a9e38c6672c3c8855f709afb0006a009ea + languageName: node + linkType: hard + "parse-json@npm:^4.0.0": version: 4.0.0 resolution: "parse-json@npm:4.0.0" @@ -1076,6 +1721,16 @@ __metadata: languageName: node linkType: hard +"path-scurry@npm:^1.11.1": + version: 1.11.1 + resolution: "path-scurry@npm:1.11.1" + dependencies: + lru-cache: ^10.2.0 + minipass: ^5.0.0 || ^6.0.2 || ^7.0.0 + checksum: 890d5abcd593a7912dcce7cf7c6bf7a0b5648e3dee6caf0712c126ca0a65c7f3d7b9d769072a4d1baf370f61ce493ab5b038d59988688e0c5f3f646ee3c69023 + languageName: node + linkType: hard + "path-type@npm:^3.0.0": version: 3.0.0 resolution: "path-type@npm:3.0.0" @@ -1126,6 +1781,30 @@ __metadata: languageName: node linkType: hard +"proc-log@npm:^3.0.0": + version: 3.0.0 + resolution: "proc-log@npm:3.0.0" + checksum: 02b64e1b3919e63df06f836b98d3af002b5cd92655cab18b5746e37374bfb73e03b84fe305454614b34c25b485cc687a9eebdccf0242cda8fda2475dd2c97e02 + languageName: node + linkType: hard + +"proc-log@npm:^4.2.0": + version: 4.2.0 + resolution: "proc-log@npm:4.2.0" + checksum: 98f6cd012d54b5334144c5255ecb941ee171744f45fca8b43b58ae5a0c1af07352475f481cadd9848e7f0250376ee584f6aa0951a856ff8f021bdfbff4eb33fc + languageName: node + linkType: hard + +"promise-retry@npm:^2.0.1": + version: 2.0.1 + resolution: "promise-retry@npm:2.0.1" + dependencies: + err-code: ^2.0.2 + retry: ^0.12.0 + checksum: f96a3f6d90b92b568a26f71e966cbbc0f63ab85ea6ff6c81284dc869b41510e6cdef99b6b65f9030f0db422bf7c96652a3fff9f2e8fb4a0f069d8f4430359429 + languageName: node + linkType: hard + "prompts@npm:^2.4.2": version: 2.4.2 resolution: "prompts@npm:2.4.2" @@ -1154,6 +1833,13 @@ __metadata: languageName: node linkType: hard +"resolve-pkg-maps@npm:^1.0.0": + version: 1.0.0 + resolution: "resolve-pkg-maps@npm:1.0.0" + checksum: 1012afc566b3fdb190a6309cc37ef3b2dcc35dff5fa6683a9d00cd25c3247edfbc4691b91078c97adc82a29b77a2660c30d791d65dab4fc78bfc473f60289977 + languageName: node + linkType: hard + "resolve@npm:^1.10.0": version: 1.22.4 resolution: "resolve@npm:1.22.4" @@ -1190,6 +1876,13 @@ __metadata: languageName: node linkType: hard +"retry@npm:^0.12.0": + version: 0.12.0 + resolution: "retry@npm:0.12.0" + checksum: 623bd7d2e5119467ba66202d733ec3c2e2e26568074923bc0585b6b99db14f357e79bdedb63cab56cec47491c4a0da7e6021a7465ca6dc4f481d3898fdd3158c + languageName: node + linkType: hard + "rfdc@npm:^1.3.0": version: 1.3.0 resolution: "rfdc@npm:1.3.0" @@ -1208,6 +1901,13 @@ __metadata: languageName: node linkType: hard +"safer-buffer@npm:>= 2.1.2 < 3.0.0": + version: 2.1.2 + resolution: "safer-buffer@npm:2.1.2" + checksum: cab8f25ae6f1434abee8d80023d7e72b598cf1327164ddab31003c51215526801e40b66c5e65d658a0af1e9d6478cadcb4c745f4bd6751f97d8644786c0978b0 + languageName: node + linkType: hard + "semver@npm:2 || 3 || 4 || 5, semver@npm:^5.5.0": version: 5.7.2 resolution: "semver@npm:5.7.2" @@ -1217,14 +1917,12 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.5.3": - version: 7.5.4 - resolution: "semver@npm:7.5.4" - dependencies: - lru-cache: ^6.0.0 +"semver@npm:^7.3.5, semver@npm:^7.5.3": + version: 7.6.2 + resolution: "semver@npm:7.6.2" bin: semver: bin/semver.js - checksum: 12d8ad952fa353b0995bf180cdac205a4068b759a140e5d3c608317098b3575ac2f1e09182206bf2eb26120e1c0ed8fb92c48c592f6099680de56bb071423ca3 + checksum: 40f6a95101e8d854357a644da1b8dd9d93ce786d5c6a77227bc69dbb17bea83d0d1d1d7c4cd5920a6df909f48e8bd8a5909869535007f90278289f2451d0292d languageName: node linkType: hard @@ -1274,6 +1972,13 @@ __metadata: languageName: node linkType: hard +"signal-exit@npm:^4.0.1": + version: 4.1.0 + resolution: "signal-exit@npm:4.1.0" + checksum: 64c757b498cb8629ffa5f75485340594d2f8189e9b08700e69199069c8e3070fb3e255f7ab873c05dc0b3cec412aea7402e10a5990cb6a050bd33ba062a6c549 + languageName: node + linkType: hard + "sisteransi@npm:^1.0.5": version: 1.0.5 resolution: "sisteransi@npm:1.0.5" @@ -1291,6 +1996,34 @@ __metadata: languageName: node linkType: hard +"smart-buffer@npm:^4.2.0": + version: 4.2.0 + resolution: "smart-buffer@npm:4.2.0" + checksum: b5167a7142c1da704c0e3af85c402002b597081dd9575031a90b4f229ca5678e9a36e8a374f1814c8156a725d17008ae3bde63b92f9cfd132526379e580bec8b + languageName: node + linkType: hard + +"socks-proxy-agent@npm:^8.0.3": + version: 8.0.4 + resolution: "socks-proxy-agent@npm:8.0.4" + dependencies: + agent-base: ^7.1.1 + debug: ^4.3.4 + socks: ^2.8.3 + checksum: b2ec5051d85fe49072f9a250c427e0e9571fd09d5db133819192d078fd291276e1f0f50f6dbc04329b207738b1071314cee8bdbb4b12e27de42dbcf1d4233c67 + languageName: node + linkType: hard + +"socks@npm:^2.8.3": + version: 2.8.3 + resolution: "socks@npm:2.8.3" + dependencies: + ip-address: ^9.0.5 + smart-buffer: ^4.2.0 + checksum: 7a6b7f6eedf7482b9e4597d9a20e09505824208006ea8f2c49b71657427f3c137ca2ae662089baa73e1971c62322d535d9d0cf1c9235cf6f55e315c18203eadd + languageName: node + linkType: hard + "spdx-correct@npm:^3.0.0": version: 3.2.0 resolution: "spdx-correct@npm:3.2.0" @@ -1325,6 +2058,22 @@ __metadata: languageName: node linkType: hard +"sprintf-js@npm:^1.1.3": + version: 1.1.3 + resolution: "sprintf-js@npm:1.1.3" + checksum: a3fdac7b49643875b70864a9d9b469d87a40dfeaf5d34d9d0c5b1cda5fd7d065531fcb43c76357d62254c57184a7b151954156563a4d6a747015cfb41021cad0 + languageName: node + linkType: hard + +"ssri@npm:^10.0.0": + version: 10.0.6 + resolution: "ssri@npm:10.0.6" + dependencies: + minipass: ^7.0.3 + checksum: 4603d53a05bcd44188747d38f1cc43833b9951b5a1ee43ba50535bdfc5fe4a0897472dbe69837570a5417c3c073377ef4f8c1a272683b401857f72738ee57299 + languageName: node + linkType: hard + "string-argv@npm:0.3.2": version: 0.3.2 resolution: "string-argv@npm:0.3.2" @@ -1332,7 +2081,7 @@ __metadata: languageName: node linkType: hard -"string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": +"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": version: 4.2.3 resolution: "string-width@npm:4.2.3" dependencies: @@ -1343,7 +2092,7 @@ __metadata: languageName: node linkType: hard -"string-width@npm:^5.0.0, string-width@npm:^5.0.1": +"string-width@npm:^5.0.0, string-width@npm:^5.0.1, string-width@npm:^5.1.2": version: 5.1.2 resolution: "string-width@npm:5.1.2" dependencies: @@ -1363,7 +2112,7 @@ __metadata: languageName: node linkType: hard -"strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": version: 6.0.1 resolution: "strip-ansi@npm:6.0.1" dependencies: @@ -1420,6 +2169,20 @@ __metadata: languageName: node linkType: hard +"tar@npm:^6.1.11, tar@npm:^6.1.2": + version: 6.2.1 + resolution: "tar@npm:6.2.1" + dependencies: + chownr: ^2.0.0 + fs-minipass: ^2.0.0 + minipass: ^5.0.0 + minizlib: ^2.1.1 + mkdirp: ^1.0.3 + yallist: ^4.0.0 + checksum: f1322768c9741a25356c11373bce918483f40fa9a25c69c59410c8a1247632487edef5fe76c5f12ac51a6356d2f1829e96d2bc34098668a2fc34d76050ac2b6c + languageName: node + linkType: hard + "test-exclude@npm:^6.0.0": version: 6.0.0 resolution: "test-exclude@npm:6.0.0" @@ -1440,41 +2203,19 @@ __metadata: languageName: node linkType: hard -"ts-node@npm:^10.9.2": - version: 10.9.2 - resolution: "ts-node@npm:10.9.2" - dependencies: - "@cspotcode/source-map-support": ^0.8.0 - "@tsconfig/node10": ^1.0.7 - "@tsconfig/node12": ^1.0.7 - "@tsconfig/node14": ^1.0.0 - "@tsconfig/node16": ^1.0.2 - acorn: ^8.4.1 - acorn-walk: ^8.1.1 - arg: ^4.1.0 - create-require: ^1.1.0 - diff: ^4.0.1 - make-error: ^1.1.1 - v8-compile-cache-lib: ^3.0.1 - yn: 3.1.1 - peerDependencies: - "@swc/core": ">=1.2.50" - "@swc/wasm": ">=1.2.50" - "@types/node": "*" - typescript: ">=2.7" - peerDependenciesMeta: - "@swc/core": - optional: true - "@swc/wasm": +"tsx@npm:^4.16.2": + version: 4.16.2 + resolution: "tsx@npm:4.16.2" + dependencies: + esbuild: ~0.21.5 + fsevents: ~2.3.3 + get-tsconfig: ^4.7.5 + dependenciesMeta: + fsevents: optional: true bin: - ts-node: dist/bin.js - ts-node-cwd: dist/bin-cwd.js - ts-node-esm: dist/bin-esm.js - ts-node-script: dist/bin-script.js - ts-node-transpile-only: dist/bin-transpile.js - ts-script: dist/bin-script-deprecated.js - checksum: fde256c9073969e234526e2cfead42591b9a2aec5222bac154b0de2fa9e4ceb30efcd717ee8bc785a56f3a119bdd5aa27b333d9dbec94ed254bd26f8944c67ac + tsx: dist/cli.mjs + checksum: bd481097d4614b9d40e7e2c44f7078d9f92b0050e959574a7a88ad8af33327d787f9d76c89689ee19b1275270038705fb9851055ae1a20e15d1c62a34d51a8fd languageName: node linkType: hard @@ -1505,10 +2246,21 @@ __metadata: languageName: node linkType: hard -"v8-compile-cache-lib@npm:^3.0.1": - version: 3.0.1 - resolution: "v8-compile-cache-lib@npm:3.0.1" - checksum: 78089ad549e21bcdbfca10c08850022b22024cdcc2da9b168bcf5a73a6ed7bf01a9cebb9eac28e03cd23a684d81e0502797e88f3ccd27a32aeab1cfc44c39da0 +"unique-filename@npm:^3.0.0": + version: 3.0.0 + resolution: "unique-filename@npm:3.0.0" + dependencies: + unique-slug: ^4.0.0 + checksum: 8e2f59b356cb2e54aab14ff98a51ac6c45781d15ceaab6d4f1c2228b780193dc70fae4463ce9e1df4479cb9d3304d7c2043a3fb905bdeca71cc7e8ce27e063df + languageName: node + linkType: hard + +"unique-slug@npm:^4.0.0": + version: 4.0.0 + resolution: "unique-slug@npm:4.0.0" + dependencies: + imurmurhash: ^0.1.4 + checksum: 0884b58365af59f89739e6f71e3feacb5b1b41f2df2d842d0757933620e6de08eff347d27e9d499b43c40476cbaf7988638d3acb2ffbcb9d35fd035591adfd15 languageName: node linkType: hard @@ -1555,7 +2307,18 @@ __metadata: languageName: node linkType: hard -"wrap-ansi@npm:^7.0.0": +"which@npm:^4.0.0": + version: 4.0.0 + resolution: "which@npm:4.0.0" + dependencies: + isexe: ^3.1.1 + bin: + node-which: bin/which.js + checksum: f17e84c042592c21e23c8195108cff18c64050b9efb8459589116999ea9da6dd1509e6a1bac3aeebefd137be00fabbb61b5c2bc0aa0f8526f32b58ee2f545651 + languageName: node + linkType: hard + +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0, wrap-ansi@npm:^7.0.0": version: 7.0.0 resolution: "wrap-ansi@npm:7.0.0" dependencies: @@ -1627,19 +2390,12 @@ __metadata: languageName: node linkType: hard -"yjs@npm:^13.6.8": - version: 13.6.8 - resolution: "yjs@npm:13.6.8" +"yjs@npm:^13.6.18": + version: 13.6.18 + resolution: "yjs@npm:13.6.18" dependencies: - lib0: ^0.2.74 - checksum: a2a6fd17a2cce6461b64bedd69f66845b9dfd4702e285be0b5e382840337232e54ba5cf5d48f871263074de625d3902d17ab8a1766695af3fc05a0b4da8d95e0 - languageName: node - linkType: hard - -"yn@npm:3.1.1": - version: 3.1.1 - resolution: "yn@npm:3.1.1" - checksum: 2c487b0e149e746ef48cda9f8bad10fc83693cd69d7f9dcd8be4214e985de33a29c9e24f3c0d6bcf2288427040a8947406ab27f7af67ee9456e6b84854f02dd6 + lib0: ^0.2.86 + checksum: 5c9f8f31f5f9f30f17680a765b015e4274820fe10fb6bf6a7d39dee2ff0493a81ace02d11bff6f18c6799cade2bcfc9fc2d7b6ca8bc1eb167c4ac2f3789c0f01 languageName: node linkType: hard From 67db9814466e6a0b7b26f7b76f242162a80e790a Mon Sep 17 00:00:00 2001 From: DarkSky Date: Mon, 1 Jul 2024 14:55:44 +0800 Subject: [PATCH 02/75] feat: impl common fn for array --- y-octo-node/index.d.ts | 7 +++-- y-octo-node/src/array.rs | 44 ++++++++++++++++++++++++++------ y-octo-node/tests/array.spec.mts | 14 +++++++++- 3 files changed, 54 insertions(+), 11 deletions(-) diff --git a/y-octo-node/index.d.ts b/y-octo-node/index.d.ts index 10a5a16..1aaf166 100644 --- a/y-octo-node/index.d.ts +++ b/y-octo-node/index.d.ts @@ -4,12 +4,15 @@ /* auto-generated by NAPI-RS */ export class YArray { - constructor() get length(): number get isEmpty(): boolean get(index: number): T + slice(start: number, end: number): Array + map(callback: (...args: any[]) => any): Array insert(index: number, value: YArray | YMap | YText | boolean | number | string | Record | null | undefined): void - remove(index: number, len: number): void + push(value: YArray | YMap | YText | boolean | number | string | Record | null | undefined): void + unshift(value: YArray | YMap | YText | boolean | number | string | Record | null | undefined): void + delete(index: number, len: number): void toJson(): JsArray } export class Doc { diff --git a/y-octo-node/src/array.rs b/y-octo-node/src/array.rs index 2a10396..37326e7 100644 --- a/y-octo-node/src/array.rs +++ b/y-octo-node/src/array.rs @@ -1,4 +1,4 @@ -use napi::{bindgen_prelude::Array as JsArray, Env, JsUnknown, ValueType}; +use napi::{bindgen_prelude::Array as JsArray, Env, JsFunction, JsUnknown, ValueType}; use y_octo::{Any, Array, Value}; use super::*; @@ -10,12 +10,6 @@ pub struct YArray { #[napi] impl YArray { - #[allow(clippy::new_without_default)] - #[napi(constructor)] - pub fn new() -> Self { - unimplemented!() - } - pub(crate) fn inner_new(array: Array) -> Self { Self { array } } @@ -46,6 +40,26 @@ impl YArray { } } + #[napi(ts_generic_types = "T = unknown", ts_return_type = "Array")] + pub fn slice(&self, env: Env, start: i64, end: i64) -> Result { + let mut js_array = env.create_array(0)?; + for value in self.array.iter().skip(start as usize).take((end - start) as usize) { + js_array.insert(get_js_unknown_from_value(env, value)?)?; + } + Ok(js_array) + } + + #[napi(ts_generic_types = "T = unknown", ts_return_type = "Array")] + pub fn map(&self, env: Env, callback: JsFunction) -> Result { + let mut js_array = env.create_array(0)?; + for value in self.array.iter() { + let js_value = get_js_unknown_from_value(env, value)?; + let result = callback.call(None, &[js_value.into_unknown()])?; + js_array.insert(result)?; + } + Ok(js_array) + } + #[napi( ts_args_type = "index: number, value: YArray | YMap | YText | boolean | number | string | Record \ | null | undefined" @@ -113,8 +127,22 @@ impl YArray { } } + #[napi( + ts_args_type = "value: YArray | YMap | YText | boolean | number | string | Record | null | undefined" + )] + pub fn push(&mut self, value: MixedRefYType) -> Result<()> { + self.insert(self.length(), value) + } + + #[napi( + ts_args_type = "value: YArray | YMap | YText | boolean | number | string | Record | null | undefined" + )] + pub fn unshift(&mut self, value: MixedRefYType) -> Result<()> { + self.insert(0, value) + } + #[napi] - pub fn remove(&mut self, index: i64, len: i64) -> Result<()> { + pub fn delete(&mut self, index: i64, len: i64) -> Result<()> { self.array.remove(index as u64, len as u64).map_err(anyhow::Error::from) } diff --git a/y-octo-node/tests/array.spec.mts b/y-octo-node/tests/array.spec.mts index 7475e81..ae2aa47 100644 --- a/y-octo-node/tests/array.spec.mts +++ b/y-octo-node/tests/array.spec.mts @@ -35,9 +35,14 @@ test("array test", { concurrency: false }, async (t) => { equal(arr.get(2), 1); equal(arr.get(3), "hello world"); equal(arr.length, 4); - arr.remove(1, 1); + arr.delete(1, 1); equal(arr.length, 3); equal(arr.get(2), "hello world"); + deepEqual(arr.slice(1, 3), [1, "hello world"]); + deepEqual( + arr.map((v) => v), + [true, 1, "hello world"], + ); }); await t.test("sub array should can edit", () => { @@ -58,5 +63,12 @@ test("array test", { concurrency: false }, async (t) => { equal(sub2.get(2), 1); equal(sub2.get(3), "hello world"); equal(sub2.length, 4); + deepEqual(sub2.slice(1, 3), [false, 1]); + deepEqual( + sub2.map((v) => v), + [true, false, 1, "hello world"], + ); + }); + }); }); From 31397fbf7061b869d97e8e6e4bc58666d2372f40 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Mon, 1 Jul 2024 15:28:09 +0800 Subject: [PATCH 03/75] chore: reorganize yjs's type define --- y-octo-node/yjs.d.ts | 3852 +++++++++++++++++++++--------------------- 1 file changed, 1955 insertions(+), 1897 deletions(-) diff --git a/y-octo-node/yjs.d.ts b/y-octo-node/yjs.d.ts index 48a82fb..b34cce3 100644 --- a/y-octo-node/yjs.d.ts +++ b/y-octo-node/yjs.d.ts @@ -8,2000 +8,2058 @@ import { ObservableV2 } from "lib0/observable"; import * as decoding from "lib0/decoding"; import * as encoding from "lib0/encoding"; -/** - * This is an abstract interface that all Connectors should implement to keep them interchangeable. - * - * @note This interface is experimental and it is not advised to actually inherit this class. - * It just serves as typing information. - */ -export class AbstractConnector extends ObservableV2 { - constructor(ydoc: Doc, awareness: any); - doc: Doc; - awareness: any; -} - -export class DeleteItem { - constructor(clock: number, len: number); - clock: number; - len: number; -} -/** - * 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 { - clients: Map>; -} -export function iterateDeletedStructs( - transaction: Transaction, - ds: DeleteSet, - f: (arg0: InternalStruct.GC | InternalStruct.Item) => void, -): void; -export function isDeleted(ds: DeleteSet, id: ID): boolean; -export function mergeDeleteSets(dss: Array): DeleteSet; -export function createDeleteSet(): DeleteSet; -export function createDeleteSetFromStructStore(ss: StructStore): DeleteSet; -export function equalDeleteSets(ds1: DeleteSet, ds2: DeleteSet): boolean; - -/** - * A Yjs instance handles the state of shared data. - */ -export class Doc extends ObservableV2 { - constructor(opts?: DocOpts); - gc: boolean; - gcFilter: (arg0: InternalStruct.Item) => boolean; - clientID: number; - guid: string; - collectionid: string | null; - share: Map>>; - store: StructStore; - subdocs: Set; - shouldLoad: boolean; - autoLoad: boolean; - meta: any; - /** - * 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. - */ - isLoaded: boolean; - /** - * 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). - */ - isSynced: boolean; - /** - * Promise that resolves once the document has been loaded from a presistence provider. - */ - whenLoaded: Promise; - whenSynced: Promise; - /** - * Notify the parent document that you request to load data into this subdocument (if it is a subdocument). - * - * `load()` might be used in the future to request any provider to load the most current data. - * - * It is safe to call `load()` multiple times. - */ - load(): void; - getSubdocs(): Set; - getSubdocGuids(): Set; - /** - * Changes that happen inside of a transaction are bundled. This means that - * the observer fires _after_ the transaction is finished and that all changes - * that happened inside of the transaction are sent as one message to the - * other peers. - * - * @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 - * - * @public - */ - transact(f: (arg0: Transaction) => T, origin?: any): T; - /** - * Define a shared data type. - * - * Multiple calls of `ydoc.get(name, TypeConstructor)` yield the same result - * and do not overwrite each other. I.e. - * `ydoc.get(name, Y.Array) === ydoc.get(name, Y.Array)` - * - * After this method is called, the type is also available on `ydoc.share.get(name)`. - * - * *Best Practices:* - * 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 {typeof AbstractType} Type - * @example - * const ydoc = new Y.Doc(..) - * const appState = { - * document: ydoc.getText('document') - * comments: ydoc.getArray('comments') - * } - * - * @param {Type} TypeConstructor The constructor of the type definition. E.g. Y.Text, Y.Array, Y.Map, ... - * @return {InstanceType} The created type. Constructed with TypeConstructor - */ - get< - Type extends { - new (): AbstractType; - }, - >(name: string, TypeConstructor?: Type): InstanceType; - getArray(name?: string | undefined): YArray; - getText(name?: string | undefined): YText; - getMap(name?: string | undefined): YMap; - getXmlElement(name?: string | undefined): YXml.YXmlElement; - getXmlFragment(name?: string | undefined): YXml.YXmlFragment; - /** - * Converts the entire document into a js object, recursively traversing each yjs type - * Doesn't log types that have not been defined (using ydoc.getType(..)). - * - * @deprecated Do not use this method and rather call toJSON directly on the shared types. - */ - toJSON(): { - [x: string]: any; - }; -} -type DocOpts = { - /** - * Disable garbage collection (default: gc=true) - */ - gc?: boolean | undefined; - /** - * Will be called before an Item is garbage collected. Return false to keep the Item. - */ - gcFilter?: ((arg0: InternalStruct.Item) => boolean) | undefined; - /** - * Define a globally unique identifier for this document - */ - guid?: string | undefined; - /** - * Associate this document with a collection. This only plays a role if your provider has a concept of collection. - */ - collectionid?: string | null | undefined; - /** - * 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. - */ - meta?: any; - /** - * If a subdocument, automatically load document. If this is a subdocument, remote peers will load the document as well automatically. - */ - autoLoad?: boolean | undefined; - /** - * Whether the document should be synced by the provider now. This is toggled to true when you call ydoc.load() - */ - shouldLoad?: boolean | undefined; -}; -type DocEvents = { - destroy: (arg0: Doc) => void; - load: (arg0: Doc) => void; - sync: (arg0: boolean, arg1: Doc) => void; - update: (arg0: Uint8Array, arg1: any, arg2: Doc, arg3: Transaction) => void; - updateV2: (arg0: Uint8Array, arg1: any, arg2: Doc, arg3: Transaction) => void; - beforeAllTransactions: (arg0: Doc) => void; - beforeTransaction: (arg0: Transaction, arg1: Doc) => void; - beforeObserverCalls: (arg0: Transaction, arg1: Doc) => void; - afterTransaction: (arg0: Transaction, arg1: Doc) => void; - afterTransactionCleanup: (arg0: Transaction, arg1: Doc) => void; - afterAllTransactions: (arg0: Doc, arg1: Array) => void; - subdocs: ( - arg0: { - loaded: Set; - added: Set; - removed: Set; - }, - arg1: Doc, - arg2: Transaction, - ) => void; -}; - -export class DSDecoderV1 { - constructor(decoder: decoding.Decoder); - restDecoder: decoding.Decoder; - resetDsCurVal(): void; - readDsClock(): number; - readDsLen(): number; -} -export class UpdateDecoderV1 extends DSDecoderV1 { - readLeftID(): ID; - readRightID(): ID; - /** - * Read the next client id. - * Use this in favor of readID whenever possible to reduce the number of objects created. - */ - readClient(): number; - readInfo(): number; - readString(): string; - /** - * @return {boolean} isKey - */ - readParentInfo(): boolean; - readTypeRef(): number; - /** - * Write len of a struct - well suited for Opt RLE encoder. - */ - readLen(): number; - readAny(): any; - readBuf(): Uint8Array; - /** - * Legacy implementation uses JSON parse. We use any-decoding in v2. - */ - readJSON(): any; - readKey(): string; -} -export class DSDecoderV2 { - constructor(decoder: decoding.Decoder); - restDecoder: decoding.Decoder; - resetDsCurVal(): void; - readDsClock(): number; - readDsLen(): number; -} -export class UpdateDecoderV2 extends DSDecoderV2 { - /** - * List of cached keys. If the keys[id] does not exist, we read a new key - * from stringEncoder and push it to keys. - */ - keys: Array; - keyClockDecoder: decoding.IntDiffOptRleDecoder; - clientDecoder: decoding.UintOptRleDecoder; - leftClockDecoder: decoding.IntDiffOptRleDecoder; - rightClockDecoder: decoding.IntDiffOptRleDecoder; - infoDecoder: decoding.RleDecoder; - stringDecoder: decoding.StringDecoder; - parentInfoDecoder: decoding.RleDecoder; - typeRefDecoder: decoding.UintOptRleDecoder; - lenDecoder: decoding.UintOptRleDecoder; - readLeftID(): ID; - readRightID(): ID; - /** - * Read the next client id. - * Use this in favor of readID whenever possible to reduce the number of objects created. - */ - readClient(): number; - readInfo(): number; - readString(): string; - readParentInfo(): boolean; - readTypeRef(): number; - /** - * Write len of a struct - well suited for Opt RLE encoder. - */ - readLen(): number; - readAny(): any; - readBuf(): Uint8Array; - /** - * This is mainly here for legacy purposes. - * - * Initial we incoded objects using JSON. Now we use the much faster lib0/any-encoder. This method mainly exists for legacy purposes for the v1 encoder. - */ - readJSON(): any; - readKey(): string; -} - -export class DSEncoderV1 { - restEncoder: encoding.Encoder; - toUint8Array(): Uint8Array; - resetDsCurVal(): void; - writeDsClock(clock: number): void; - writeDsLen(len: number): void; -} -export class UpdateEncoderV1 extends DSEncoderV1 { - writeLeftID(id: ID): void; - writeRightID(id: ID): void; - /** - * Use writeClient and writeClock instead of writeID if possible. - */ - writeClient(client: number): void; - writeInfo(info: number): void; - writeString(s: string): void; - writeParentInfo(isYKey: boolean): void; - writeTypeRef(info: number): void; - /** - * Write len of a struct - well suited for Opt RLE encoder. - */ - writeLen(len: number): void; - writeAny(any: any): void; - writeBuf(buf: Uint8Array): void; - writeJSON(embed: any): void; - writeKey(key: string): void; -} -export class DSEncoderV2 { - restEncoder: encoding.Encoder; - dsCurrVal: number; - toUint8Array(): Uint8Array; - resetDsCurVal(): void; - /** - * @param {number} clock - */ - writeDsClock(clock: number): void; - /** - * @param {number} len - */ - writeDsLen(len: number): void; -} -export class UpdateEncoderV2 extends DSEncoderV2 { - /** - * @type {Map} - */ - keyMap: Map; - /** - * Refers to the next uniqe key-identifier to me used. - * See writeKey method for more information. - * - * @type {number} - */ - keyClock: number; - keyClockEncoder: encoding.IntDiffOptRleEncoder; - clientEncoder: encoding.UintOptRleEncoder; - leftClockEncoder: encoding.IntDiffOptRleEncoder; - rightClockEncoder: encoding.IntDiffOptRleEncoder; - infoEncoder: encoding.RleEncoder; - stringEncoder: encoding.StringEncoder; - parentInfoEncoder: encoding.RleEncoder; - typeRefEncoder: encoding.UintOptRleEncoder; - lenEncoder: encoding.UintOptRleEncoder; - writeLeftID(id: ID): void; - writeRightID(id: ID): void; - writeClient(client: number): void; - writeInfo(info: number): void; - writeString(s: string): void; - writeParentInfo(isYKey: boolean): void; - writeTypeRef(info: number): void; - /** - * Write len of a struct - well suited for Opt RLE encoder. - */ - writeLen(len: number): void; - writeAny(any: any): void; - writeBuf(buf: Uint8Array): void; - /** - * This is mainly here for legacy purposes. - * - * Initial we incoded objects using JSON. Now we use the much faster lib0/any-encoder. This method mainly exists for legacy purposes for the v1 encoder. - */ - writeJSON(embed: any): void; - /** - * Property keys are often reused. For example, in y-prosemirror the key `bold` might - * occur very often. For a 3d application, the key `position` might occur very often. - * - * We cache these keys in a Map and refer to them via a unique number. - */ - writeKey(key: string): void; -} - -export function readUpdateV2( - decoder: decoding.Decoder, - ydoc: Doc, - transactionOrigin?: any, - structDecoder?: UpdateDecoderV1 | UpdateDecoderV2 | undefined, -): void; -export function readUpdate( - decoder: decoding.Decoder, - ydoc: Doc, - transactionOrigin?: any, -): void; -export function applyUpdateV2( - ydoc: Doc, - update: Uint8Array, - transactionOrigin?: any, - YDecoder?: typeof UpdateDecoderV1 | typeof UpdateDecoderV2 | undefined, -): void; -export function applyUpdate( - ydoc: Doc, - update: Uint8Array, - transactionOrigin?: any, -): void; -export function encodeStateAsUpdateV2( - doc: Doc, - encodedTargetStateVector?: Uint8Array | undefined, - encoder?: UpdateEncoderV2 | UpdateEncoderV1 | undefined, -): Uint8Array; -export function encodeStateAsUpdate( - doc: Doc, - encodedTargetStateVector?: Uint8Array | undefined, -): Uint8Array; -export function decodeStateVector( - decodedState: Uint8Array, -): Map; -export function encodeStateVectorV2( - doc: Doc | Map, - encoder?: DSEncoderV1 | DSEncoderV2 | undefined, -): Uint8Array; -export function encodeStateVector(doc: Doc | Map): Uint8Array; - -export class ID { - /** - * @param {number} client client id - * @param {number} clock unique per client id, continuous number - */ - constructor(client: number, clock: number); - /** - * Client id - */ - client: number; - /** - * unique per client id, continuous number - */ - clock: number; -} -export function compareIDs(a: ID | null, b: ID | null): boolean; -export function createID(client: number, clock: number): ID; -export function findRootTypeKey(type: AbstractType): string; - -export function isParentOf( - parent: AbstractType, - child: InternalStruct.Item | null, -): boolean; - -export function logType(type: AbstractType): void; - -export class PermanentUserData { - constructor(doc: Doc, storeType?: YMap | undefined); - yusers: YMap; - doc: Doc; - /** - * Maps from clientid to userDescription - */ - clients: Map; - dss: Map; - setUserMapping( - doc: Doc, - clientid: number, - userDescription: string, - conf?: { - filter?: ((arg0: Transaction, arg1: DeleteSet) => boolean) | undefined; - }, - ): void; - getUserByClientId(clientid: number): any; - getUserByDeletedId(id: ID): string | null; -} -declare namespace RelativePosition { - /** - * A relative position is based on the Yjs model and is not affected by document changes. - * E.g. If you place a relative position before a certain character, it will always point to this character. - * If you place a relative position at the end of a type, it will always point to the end of the type. - * - * A numeric position is often unsuited for user selections, because it does not change when content is inserted - * before or after. - * - * ```Insert(0, 'x')('a|bc') = 'xa|bc'``` Where | is the relative position. - * - * One of the properties must be defined. - * - * @example - * // Current cursor position is at position 10 - * const relativePosition = createRelativePositionFromIndex(yText, 10) - * // modify yText - * yText.insert(0, 'abc') - * yText.delete(3, 10) - * // Compute the cursor position - * const absolutePosition = createAbsolutePositionFromRelativePosition(y, relativePosition) - * absolutePosition.type === yText // => true - * console.log('cursor location is ' + absolutePosition.index) // => cursor location is 3 - * - */ - export class RelativePosition { - constructor( - type: ID | null, - tname: string | null, - item: ID | null, - assoc?: number, - ); - type: ID | null; - tname: string | null; - item: ID | null; - /** - * A relative position is associated to a specific character. By default - * assoc >= 0, the relative position is associated to the character - * 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 - * before the meant position. - */ - assoc: number; - } - export function relativePositionToJSON(rpos: RelativePosition): any; - export function createRelativePositionFromJSON(json: any): RelativePosition; - export class AbsolutePosition { - constructor( - type: AbstractType, - index: number, - assoc?: number | undefined, - ); - type: AbstractType; - index: number; - assoc: number; - } - export function createAbsolutePosition( - type: AbstractType, - index: number, - assoc?: number | undefined, - ): AbsolutePosition; - export function createRelativePosition( - type: AbstractType, - item: ID | null, - assoc?: number | undefined, - ): RelativePosition; - export function createRelativePositionFromTypeIndex( - type: AbstractType, - index: number, - assoc?: number | undefined, - ): RelativePosition; - export function encodeRelativePosition(rpos: RelativePosition): Uint8Array; - export function decodeRelativePosition( - uint8Array: Uint8Array, - ): RelativePosition; - export function createAbsolutePositionFromRelativePosition( - rpos: RelativePosition, - doc: Doc, - followUndoneDeletions?: boolean, - ): AbsolutePosition | null; - export function compareRelativePositions( - a: RelativePosition | null, - b: RelativePosition | null, - ): boolean; -} -declare namespace Snapshot { - export class Snapshot { - constructor(ds: DeleteSet, sv: Map); - ds: DeleteSet; - /** - * State Map - */ - sv: Map; - } - export function equalSnapshots(snap1: Snapshot, snap2: Snapshot): boolean; - export function encodeSnapshotV2( - snapshot: Snapshot, - encoder?: DSEncoderV1 | DSEncoderV2 | undefined, - ): Uint8Array; - export function encodeSnapshot(snapshot: Snapshot): Uint8Array; - export function decodeSnapshotV2( - buf: Uint8Array, - decoder?: DSDecoderV1 | DSDecoderV2 | undefined, - ): Snapshot; - export function decodeSnapshot(buf: Uint8Array): Snapshot; - export function createSnapshot( - ds: DeleteSet, - sm: Map, - ): Snapshot; - export const emptySnapshot: Snapshot; - export function snapshot(doc: Doc): Snapshot; - export function createDocFromSnapshot( - originDoc: Doc, - snapshot: Snapshot, - newDoc?: Doc | undefined, - ): Doc; - export function snapshotContainsUpdate( - snapshot: Snapshot, - update: Uint8Array, - ): boolean; - - export function typeListToArraySnapshot( - type: AbstractType, - snapshot: Snapshot, - ): Array; - export function typeMapGetSnapshot( - parent: AbstractType, - key: string, - snapshot: Snapshot, - ): - | { - [x: string]: any; - } - | number - | null - | Array - | string - | Uint8Array - | AbstractType - | undefined; - export function typeMapGetAllSnapshot( - parent: AbstractType, - snapshot: Snapshot, - ): { - [x: string]: - | { - [x: string]: any; - } - | number - | null - | Array - | string - | Uint8Array - | AbstractType - | undefined; - }; -} - -export class StructStore { - clients: Map>; - pendingStructs: { - missing: Map; - update: Uint8Array; - } | null; - pendingDs: null | Uint8Array; -} -export function getState(store: StructStore, client: number): number; -export function findIndexSS( - structs: Array, - clock: number, -): number; -export function getItem(arg0: StructStore, arg1: ID): InternalStruct.Item; - -/** - * A transaction is created for every change on the Yjs model. It is possible - * to bundle changes on the Yjs model in a single transaction to - * minimize the number on messages sent and the number of observer calls. - * If possible the user of this library should bundle as many changes as - * possible. Here is an example to illustrate the advantages of bundling: - * - * @example - * const ydoc = new Y.Doc() - * const map = ydoc.getMap('map') - * // Log content when change is triggered - * map.observe(() => { - * console.log('change triggered') - * }) - * // Each change on the map type triggers a log message: - * 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: - * ydoc.transact(() => { - * map.set('a', 1) - * map.set('b', 1) - * }) // => "change triggered" - */ -export class Transaction { - constructor(doc: Doc, origin: any, local: boolean); - /** - * The Yjs instance. - */ - doc: Doc; - /** - * Describes the set of deleted items by ids - */ - deleteSet: DeleteSet; - /** - * Holds the state before the transaction started. - */ - beforeState: Map; - /** - * Holds the state after the transaction. - */ - afterState: Map; - /** - * 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) - */ - changed: Map>, Set>; - /** - * Stores the events for the types that observe also child elements. - * It is mainly used by `observeDeep`. - */ - changedParentTypes: Map>, Array>>; - origin: any; - /** - * Stores meta information on the transaction - */ - meta: Map; - /** - * Whether this change originates from this doc. - */ - local: boolean; - subdocsAdded: Set; - subdocsRemoved: Set; - subdocsLoaded: Set; -} - -export function tryGc( - ds: DeleteSet, - store: StructStore, - gcFilter: (arg0: InternalStruct.Item) => boolean, -): void; -export function transact( - doc: Doc, - f: (arg0: Transaction) => T, - origin?: any, - local?: boolean, -): T; -declare namespace UndoManager { - export class StackItem { - constructor(deletions: DeleteSet, insertions: DeleteSet); - insertions: DeleteSet; - deletions: DeleteSet; - /** - * Use this to save and restore metadata like selection range - */ - meta: Map; - } - /** - * 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 - * metadata property on `event.stackItem.meta` (it is a `Map` of metadata properties). - * 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`. - */ - export class UndoManager extends ObservableV2<{ - "stack-item-added": (arg0: StackItemEvent, arg1: UndoManager) => void; - "stack-item-popped": (arg0: StackItemEvent, arg1: UndoManager) => void; - "stack-cleared": (arg0: { - undoStackCleared: boolean; - redoStackCleared: boolean; - }) => void; - "stack-item-updated": (arg0: StackItemEvent, arg1: UndoManager) => void; - }> { - /** - * @param {AbstractType|Array>} typeScope Accepts either a single type, or an array of types - * @param {UndoManagerOptions} options - */ - constructor( - typeScope: AbstractType | Array>, - options?: UndoManagerOptions, - ); - /** - * @type {Array>} - */ - scope: Array>; - doc: Doc; - deleteFilter: (arg0: InternalStruct.Item) => boolean; - trackedOrigins: Set; - captureTransaction: (arg0: Transaction) => boolean; - undoStack: Array; - redoStack: Array; - /** - * Whether the client is currently undoing (calling UndoManager.undo) - */ - undoing: boolean; - redoing: boolean; - /** - * The currently popped stack item if UndoManager.undoing or UndoManager.redoing - */ - currStackItem: StackItem | null; - lastChange: number; - ignoreRemoteMapChanges: boolean; - captureTimeout: number; - afterTransactionHandler: (transaction: Transaction) => void; - addToScope(ytypes: Array> | AbstractType): void; - addTrackedOrigin(origin: any): void; - removeTrackedOrigin(origin: any): void; - clear(clearUndoStack?: boolean, clearRedoStack?: boolean): void; - /** - * UndoManager merges Undo-StackItem if they are created within time-gap - * smaller than `options.captureTimeout`. Call `um.stopCapturing()` so that the next - * StackItem won't be merged. - * - * - * @example - * // without stopCapturing - * ytext.insert(0, 'a') - * ytext.insert(1, 'b') - * um.undo() - * ytext.toString() // => '' (note that 'ab' was removed) - * // with stopCapturing - * ytext.insert(0, 'a') - * um.stopCapturing() - * ytext.insert(0, 'b') - * um.undo() - * ytext.toString() // => 'a' (note that only 'b' was removed) - * - */ - stopCapturing(): void; - /** - * Undo last changes on type. - */ - undo(): StackItem | null; - /** - * Redo last undo operation. - */ - redo(): StackItem | null; - /** - * Are undo steps available? - */ - canUndo(): boolean; - /** - * Are redo steps available? - */ - canRedo(): boolean; - } - type UndoManagerOptions = { - captureTimeout?: number | undefined; - /** - * Do not capture changes of a Transaction if result false. - */ - captureTransaction?: ((arg0: Transaction) => boolean) | undefined; - /** - * Sometimes - * it is necessary to filter what an Undo/Redo operation can delete. If this - * filter returns false, the type/item won't be deleted even it is in the - * undo/redo scope. - */ - deleteFilter?: ((arg0: InternalStruct.Item) => boolean) | undefined; - trackedOrigins?: Set | undefined; - /** - * Experimental. By default, the UndoManager will never overwrite remote changes. Enable this property to enable overwriting remote changes on key-value changes (Y.Map, properties on Y.Xml, etc..). - */ - ignoreRemoteMapChanges?: boolean | undefined; - /** - * The document that this UndoManager operates on. Only needed if typeScope is empty. - */ - doc?: Doc | undefined; - }; - type StackItemEvent = { - stackItem: StackItem; - origin: any; - type: "undo" | "redo"; - changedParentTypes: Map>, Array>>; - }; -} - -export function logUpdate(update: Uint8Array): void; -export function logUpdateV2( - update: Uint8Array, - YDecoder?: typeof UpdateDecoderV1 | typeof UpdateDecoderV2 | undefined, -): void; -export function decodeUpdate(update: Uint8Array): { - structs: (InternalStruct.GC | InternalStruct.Item | InternalStruct.Skip)[]; - ds: DeleteSet; -}; -export function decodeUpdateV2( - update: Uint8Array, - YDecoder?: typeof UpdateDecoderV1 | typeof UpdateDecoderV2 | undefined, -): { - structs: (InternalStruct.GC | InternalStruct.Item | InternalStruct.Skip)[]; - ds: DeleteSet; -}; - -export function mergeUpdates(updates: Array): Uint8Array; -export function encodeStateVectorFromUpdateV2( - update: Uint8Array, - YEncoder?: typeof DSEncoderV1 | typeof DSEncoderV2, - YDecoder?: typeof UpdateDecoderV1 | typeof UpdateDecoderV2, -): Uint8Array; -export function encodeStateVectorFromUpdate(update: Uint8Array): Uint8Array; -export function parseUpdateMetaV2( - update: Uint8Array, - YDecoder?: typeof UpdateDecoderV1 | typeof UpdateDecoderV2, -): { - from: Map; - to: Map; -}; -export function parseUpdateMeta(update: Uint8Array): { - from: Map; - to: Map; -}; -export function mergeUpdatesV2( - updates: Array, - YDecoder?: typeof UpdateDecoderV1 | typeof UpdateDecoderV2 | undefined, - YEncoder?: typeof UpdateEncoderV2 | typeof UpdateEncoderV1 | undefined, -): Uint8Array; -export function diffUpdateV2( - update: Uint8Array, - sv: Uint8Array, - YDecoder?: typeof UpdateDecoderV1 | typeof UpdateDecoderV2 | undefined, - YEncoder?: typeof UpdateEncoderV2 | typeof UpdateEncoderV1 | undefined, -): Uint8Array; -export function diffUpdate(update: Uint8Array, sv: Uint8Array): Uint8Array; -export function obfuscateUpdate( - update: Uint8Array, - opts?: ObfuscatorOptions | undefined, -): Uint8Array; -export function obfuscateUpdateV2( - update: Uint8Array, - opts?: ObfuscatorOptions | undefined, -): Uint8Array; -export function convertUpdateFormatV1ToV2(update: Uint8Array): Uint8Array; -export function convertUpdateFormatV2ToV1(update: Uint8Array): Uint8Array; -type ObfuscatorOptions = { - formatting?: boolean | undefined; - subdocs?: boolean | undefined; - /** - * Whether to obfuscate nodeName / hookName - */ - yxml?: boolean | undefined; -}; - -/** - * YEvent describes the changes on a YType. - */ -export class YEvent> { - /** - * @param {T} target The changed type. - * @param {Transaction} transaction - */ - constructor(target: T, transaction: Transaction); - /** - * The type on which this event was created on. - */ - target: T; - /** - * The current target on which the observe callback is called. - */ - currentTarget: AbstractType; - /** - * The transaction that triggered this event. - */ - transaction: Transaction; - /** - * Computes the path from `y` to the changed type. - * - * @todo v14 should standardize on path: Array<{parent, index}> because that is easier to work with. - * - * The following property holds: - * @example - * let type = y - * event.path.forEach(dir => { - * type = type.get(dir) - * }) - * type === event.target // => true - */ - get path(): (string | number)[]; - /** - * Check if a struct is deleted by this event. - * - * In contrast to change.deleted, this method also returns true if the struct was added and then deleted. - */ - deletes(struct: InternalStruct.AbstractStruct): boolean; - get keys(): Map< - string, - { - action: "add" | "update" | "delete"; - oldValue: any; - newValue: any; - } - >; - /** - * This is a computed property. Note that this can only be safely computed during the - * event call. Computing this property after other changes happened might result in - * unexpected behavior (incorrect computation of deltas). A safe way to collect changes - * is to store the `changes` or the `delta` object. Avoid storing the `transaction` object. - */ - get delta(): { - insert?: string | object | any[] | AbstractType | undefined; - retain?: number | undefined; - delete?: number | undefined; - attributes?: - | { - [x: string]: any; - } - | undefined; - }[]; - /** - * 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. - */ - adds(struct: InternalStruct.AbstractStruct): boolean; - /** - * This is a computed property. Note that this can only be safely computed during the - * event call. Computing this property after other changes happened might result in - * unexpected behavior (incorrect computation of deltas). A safe way to collect changes - * is to store the `changes` or the `delta` object. Avoid storing the `transaction` object. - */ - get changes(): { - added: Set; - deleted: Set; - keys: Map< - string, - { - action: "add" | "update" | "delete"; - oldValue: any; - } - >; - delta: Array<{ - insert?: Array | string; - delete?: number; - retain?: number; - }>; - }; -} - -export function getTypeChildren( - t: AbstractType, -): Array; - -/** - * Abstract Yjs Type class - */ -export class AbstractType { - doc: Doc | null; - get parent(): AbstractType | null; - - /** - * 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. - */ - clone(): AbstractType; - /** - * Observe all events that are created on this type. - */ - observe(f: (arg0: EventType, arg1: Transaction) => void): void; - /** - * Observe all events that are created by this type and its children. - */ - observeDeep(f: (arg0: Array>, arg1: Transaction) => void): void; - /** - * Unregister an observer function. - */ - unobserve(f: (arg0: EventType, arg1: Transaction) => void): void; - /** - * Unregister an observer function. - */ - unobserveDeep(f: (arg0: Array>, arg1: Transaction) => void): void; - toJSON(): any; -} - -/** - * Event that describes the changes on a YArray - */ -export class YArrayEvent extends YEvent> { - constructor(target: YArray, transaction: Transaction); -} -/** - * A shared Array implementation. - */ -export class YArray - extends AbstractType> - implements Iterable -{ - /** - * Construct a new YArray containing the specified items. - * @template {Object|Array|number|null|string|Uint8Array} T - * @param {Array} items - * @return {YArray} - */ - static from< - T_1 extends - | string - | number - | any[] - | Uint8Array - | { - [x: string]: any; - } - | null, - >(items: T_1[]): 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(): YArray; - get length(): number; - /** - * Inserts new content at an index. - * - * Important: This function expects an array of content. Not just a content - * object. The reason for this "weirdness" is that inserting several elements - * is very efficient when it is done as a single operation. - * - * @example - * // Insert character 'a' at position 0 - * yarray.insert(0, ['a']) - * // Insert numbers 1, 2 at position 1 - * yarray.insert(1, [1, 2]) - * - * @param {number} index The index to insert content at. - * @param {Array} content The array of content - */ - insert(index: number, content: Array): void; - /** - * Appends content to this YArray. - * - * @param {Array} content Array of content to append. - * - * @todo Use the following implementation in all types. - */ - push(content: Array): void; - /** - * Prepends content to this YArray. - * - * @param {Array} content Array of content to prepend. - */ - unshift(content: Array): void; - /** - * Deletes elements starting from an index. - * - * @param {number} index Index at which to start deleting elements - * @param {number} length The number of elements to remove. Defaults to 1. - */ - delete(index: number, length?: number): void; - /** - * Returns the i-th element from a YArray. - * - * @param {number} index The index of the element to return from the YArray - * @return {T} - */ - get(index: number): T; - /** - * Transforms this YArray to a JavaScript Array. - * - * @return {Array} - */ - toArray(): Array; - /** - * Returns a portion of this YArray into a JavaScript Array selected - * from start to end (end not included). - * - * @param {number} [start] - * @param {number} [end] - * @return {Array} - */ - slice(start?: number | undefined, end?: number | undefined): Array; - /** - * Transforms this Shared Type to a JSON object. - * - * @return {Array} - */ - toJSON(): Array; - /** - * Returns an Array with the result of calling a provided function on every - * element of this YArray. - * - * @template M - * @param {function(T,number,YArray):M} f Function that produces an element of the new Array - * @return {Array} A new array with each element being the result of the - * callback function - */ - map(f: (arg0: T, arg1: number, arg2: YArray) => M): M[]; - /** - * 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. - */ - forEach(f: (arg0: T, arg1: number, arg2: YArray) => void): void; - /** - * @return {IterableIterator} - */ - [Symbol.iterator](): IterableIterator; -} - -/** - * 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: YMap, transaction: Transaction, subs: Set); - keysChanged: Set; -} -/** - * A shared Map implementation. - */ -export class YMap - extends AbstractType> - implements Iterable<[string, MapType]> -{ - /** - * - * @param {Iterable=} entries - an optional iterable to initialize the YMap - */ - constructor(entries?: Iterable | undefined); - /** - * 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. - */ - clone(): YMap; - /** - * Transforms this Shared Type to a JSON object. - */ - toJSON(): { - [x: string]: any; - }; - /** - * Returns the size of the YMap (count of key/value pairs) - */ - get size(): number; - /** - * Returns the keys for each element in the YMap Type. - */ - keys(): IterableIterator; - /** - * Returns the values for each element in the YMap Type. - */ - values(): IterableIterator; - /** - * Returns an Iterator of [key, value] pairs - */ - entries(): IterableIterator<[string, MapType]>; - /** - * Executes a provided function on once on every key-value pair. - * - * @param {function(MapType,string,YMap):void} f A function to execute on every element of this YArray. - */ - forEach(f: (arg0: MapType, arg1: string, arg2: YMap) => void): void; - /** - * Remove a specified element from this YMap. - * - * @param {string} key The key of the element to remove. - */ - delete(key: string): void; - /** - * 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 {VAL} value The value of the element to add - * @return {VAL} - */ - set(key: string, value: VAL): VAL; - /** - * Returns a specified element from this YMap. - * - * @param {string} key - * @return {MapType|undefined} - */ - get(key: string): MapType | undefined; - /** - * Returns a boolean indicating whether the specified key exists or not. - * - * @param {string} key The key to test. - * @return {boolean} - */ - has(key: string): boolean; - /** - * Removes all elements from this YMap. - */ - clear(): void; - /** - * Returns an Iterator of [key, value] pairs - * - * @return {IterableIterator<[string, MapType]>} - */ - [Symbol.iterator](): IterableIterator<[string, MapType]>; -} - -export function cleanupYTextFormatting(type: YText): number; - -/** - * 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: YText, transaction: Transaction, subs: Set); - /** - * Set of all changed attributes. - */ - keysChanged: Set; - /** - * Compute the changes in the delta format. - * A {@link https://quilljs.com/docs/delta/|Quill Delta}) that represents the changes on the document. - */ - get delta(): { - insert?: string | object | AbstractType | undefined; - delete?: number | undefined; - retain?: number | undefined; - attributes?: - | { - [x: string]: any; - } - | undefined; - }[]; -} -/** - * Type that represents text with formatting information. - * - * This type replaces y-richtext as this implementation is able to handle - * block formats (format information on a paragraph), embeds (complex elements - * like pictures and videos), and text formats (**bold**, *italic*). - */ -export class YText extends AbstractType { - /** - * @param {String} [string] The initial value of the YText. - */ - constructor(string?: string | undefined); - /** - * Number of characters of this text type. - */ - get length(): number; - /** - * 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. - */ - clone(): YText; - /** - * Returns the unformatted string representation of this YText type. - */ - toJSON(): string; - /** - * 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. - */ - applyDelta(delta: any, opts?: { sanitize?: boolean | undefined }): void; - /** - * Returns the Delta representation of this YText type. - */ - toDelta( - snapshot?: Snapshot.Snapshot | undefined, - prevSnapshot?: Snapshot.Snapshot | undefined, - computeYChange?: ((arg0: "removed" | "added", arg1: ID) => any) | undefined, - ): any; - /** - * Insert text at a given index. - * - * @param {number} index The index at which to start inserting. - * @param {String} text The text to insert at the specified position. - * @param {TextAttributes} [attributes] Optionally define some formatting - * information to apply on the inserted - * Text. - * @public - */ - insert(index: number, text: string, attributes?: Object | undefined): void; - /** - * Inserts an embed at a index. - * - * @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 - * embed - */ - insertEmbed( - index: number, - embed: Object | AbstractType, - attributes?: Object | undefined, - ): void; - /** - * Deletes text starting from an index. - * - * @param {number} index Index at which to start deleting. - * @param {number} length The number of characters to remove. Defaults to 1. - * - * @public - */ - delete(index: number, length: number): void; - /** - * Assigns properties to a range of text. - * - * @param {number} index The position where to start formatting. - * @param {number} length The amount of characters to assign properties to. - * @param {TextAttributes} attributes Attribute information to apply on the - * text. - */ - format(index: number, length: number, attributes: TextAttributes): void; - /** - * Removes an attribute. - * - * @note Xml-Text nodes don't have attributes. You can use this feature to assign properties to complete text-blocks. - * - * @param {String} attributeName The attribute name that is to be removed. - */ - removeAttribute(attributeName: string): void; - /** - * Sets or updates an attribute. - * - * @note Xml-Text nodes don't have attributes. You can use this feature to assign properties to complete text-blocks. - * - * @param {String} attributeName The attribute name that is to be set. - * @param {any} attributeValue The attribute value that is to be set. - */ - setAttribute(attributeName: string, attributeValue: any): void; - /** - * Returns an attribute value that belongs to the attribute name. - * - * @note Xml-Text nodes don't have attributes. You can use this feature to assign properties to complete text-blocks. - * - * @param {String} attributeName The attribute name that identifies the - * queried value. - * @return {any} The queried attribute value. - */ - getAttribute(attributeName: string): any; - /** - * Returns all attribute name/value pairs in a JSON Object. - * - * @note Xml-Text nodes don't have attributes. You can use this feature to assign properties to complete text-blocks. - * - * @return {Object} A JSON Object that describes the attributes. - */ - getAttributes(): { - [x: string]: any; - }; -} -/** - * Attributes that can be assigned to a selection of text. - */ -export type TextAttributes = Object; - -declare namespace YXml { +declare module Y { /** - * Represents a subset of the nodes of a YXmlElement / YXmlFragment and a - * position within them. - * - * Can be created with {@link YXmlFragment#createTreeWalker} + * A Yjs instance handles the state of shared data. */ - export class YXmlTreeWalker - implements Iterable - { - constructor( - root: YXmlFragment | YXmlElement, - f?: ((arg0: AbstractType) => boolean) | undefined, - ); + export class Doc extends ObservableV2 { + constructor(opts?: DocOpts); + gc: boolean; + gcFilter: (arg0: Struct.Item) => boolean; + clientID: number; + guid: string; + collectionid: string | null; + share: Map>>; + store: Struct.StructStore; + subdocs: Set; + shouldLoad: boolean; + autoLoad: boolean; + meta: any; /** - * Get the next node. + * 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. */ - next(): IteratorResult; - [Symbol.iterator](): YXmlTreeWalker; - } - /** - * Represents a list of {@link YXmlElement}.and {@link YXmlText} types. - * A YxmlFragment is similar to a {@link YXmlElement}, but it does not have a - * nodeName and it does not have attributes. Though it can be bound to a DOM - * element - in this case the attributes and the nodeName are not shared. - */ - export class YXmlFragment extends AbstractType { - constructor(); - get firstChild(): - | YXmlElement<{ - [key: string]: string; - }> - | YXmlText - | null; + isLoaded: boolean; /** - * 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. + * 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). */ - clone(): YXmlFragment; - get length(): number; + isSynced: boolean; /** - * 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. + * Promise that resolves once the document has been loaded from a presistence provider. */ - createTreeWalker( - filter: (arg0: AbstractType) => boolean, - ): YXmlTreeWalker; + whenLoaded: Promise; + whenSynced: Promise; /** - * Returns the first YXmlElement that matches the query. - * Similar to DOM's {@link querySelector}. + * Notify the parent document that you request to load data into this subdocument (if it is a subdocument). * - * Query support: - * - tagname - * TODO: - * - id - * - attribute + * `load()` might be used in the future to request any provider to load the most current data. * - * @param {CSS_Selector} query The query on the children. - * @return {YXmlElement|YXmlText|YXmlHook|null} The first element that matches the query or null. + * It is safe to call `load()` multiple times. */ - querySelector( - query: CSS_Selector, - ): YXmlElement | YXmlText | YXmlHook | null; + load(): void; + getSubdocs(): Set; + getSubdocGuids(): Set; /** - * Returns all YXmlElements that match the query. - * Similar to Dom's {@link querySelectorAll}. + * Changes that happen inside of a transaction are bundled. This means that + * the observer fires _after_ the transaction is finished and that all changes + * that happened inside of the transaction are sent as one message to the + * other peers. * - * @todo Does not yet support all queries. Currently only query by tagName. + * @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 * - * @param {CSS_Selector} query The query on the children - * @return {Array} The elements that match this query. + * @public */ - querySelectorAll( - query: CSS_Selector, - ): Array; - toJSON(): string; + transact(f: (arg0: Transaction) => T, origin?: any): T; /** - * Creates a Dom Element that mirrors this YXmlElement. + * Define a shared data type. * - * @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} - */ - toDOM( - _document?: Document | undefined, - hooks?: - | { - [x: string]: any; - } - | undefined, - binding?: any, - ): Node; - /** - * Inserts new content at an index. + * Multiple calls of `ydoc.get(name, TypeConstructor)` yield the same result + * and do not overwrite each other. I.e. + * `ydoc.get(name, Y.Array) === ydoc.get(name, Y.Array)` * - * @example - * // Insert character 'a' at position 0 - * xml.insert(0, [new Y.XmlText('text')]) + * After this method is called, the type is also available on `ydoc.share.get(name)`. * - * @param {number} index The index to insert content at - * @param {Array} content The array of content - */ - insert(index: number, content: Array): void; - /** - * Inserts new content at an index. + * *Best Practices:* + * 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)`, .. * * @example - * // Insert character 'a' at position 0 - * xml.insert(0, [new Y.XmlText('text')]) + * const ydoc = new Y.Doc(..) + * const appState = { + * document: ydoc.getText('document') + * comments: ydoc.getArray('comments') + * } * - * @param {null|Item|YXmlElement|YXmlText} ref The index to insert content at - * @param {Array} content The array of content + * @param {Type} TypeConstructor The constructor of the type definition. E.g. Y.Text, Y.Array, Y.Map, ... + * @return {InstanceType} The created type. Constructed with TypeConstructor + */ + get }>( + name: string, + TypeConstructor?: Type, + ): InstanceType; + getArray(name?: string | undefined): Type.YArray; + getText(name?: string | undefined): Type.YText; + getMap(name?: string | undefined): Type.YMap; + getXmlElement(name?: string | undefined): Type.YXml.YXmlElement; + getXmlFragment(name?: string | undefined): Type.YXml.YXmlFragment; + /** + * Converts the entire document into a js object, recursively traversing each yjs type + * Doesn't log types that have not been defined (using ydoc.getType(..)). + * + * @deprecated Do not use this method and rather call toJSON directly on the shared types. */ - insertAfter( - ref: null | InternalStruct.Item | YXmlElement | YXmlText, - content: Array, - ): void; + toJSON(): { + [x: string]: any; + }; + } + type DocOpts = { /** - * Deletes elements starting from an index. - * - * @param {number} index Index at which to start deleting elements - * @param {number} [length=1] The number of elements to remove. Defaults to 1. + * Disable garbage collection (default: gc=true) */ - delete(index: number, length?: number | undefined): void; + gc?: boolean | undefined; /** - * Transforms this YArray to a JavaScript Array. - * - * @return {Array} + * Will be called before an Item is garbage collected. Return false to keep the Item. */ - toArray(): Array; + gcFilter?: ((arg0: Struct.Item) => boolean) | undefined; /** - * Appends content to this YArray. - * - * @param {Array} content Array of content to append. + * Define a globally unique identifier for this document */ - push(content: Array): void; + guid?: string | undefined; /** - * Prepends content to this YArray. - * - * @param {Array} content Array of content to prepend. + * Associate this document with a collection. This only plays a role if your provider has a concept of collection. */ - unshift(content: Array): void; + collectionid?: string | null | undefined; /** - * Returns the i-th element from a YArray. - * - * @param {number} index The index of the element to return from the YArray - * @return {YXmlElement|YXmlText} + * 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. */ - get(index: number): YXmlElement | YXmlText; + meta?: any; /** - * Returns a portion of this YXmlFragment into a JavaScript Array selected - * from start to end (end not included). - * - * @param {number} [start] - * @param {number} [end] - * @return {Array} + * If a subdocument, automatically load document. If this is a subdocument, remote peers will load the document as well automatically. */ - slice( - start?: number | undefined, - end?: number | undefined, - ): Array; + autoLoad?: boolean | undefined; /** - * Executes a provided function on once on every child element. - * - * @param {function(YXmlElement|YXmlText,number, typeof self):void} f A function to execute on every element of this YArray. + * Whether the document should be synced by the provider now. This is toggled to true when you call ydoc.load() */ - forEach( - f: ( - arg0: YXmlElement | YXmlText, - arg1: number, - arg2: typeof self, - ) => void, - ): void; - } - - /** - * Define the elements to which a set of CSS queries apply. - * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors|CSS_Selectors} - */ - type CSS_Selector = string; + shouldLoad?: boolean | undefined; + }; + type DocEvents = { + destroy: (arg0: Doc) => void; + load: (arg0: Doc) => void; + sync: (arg0: boolean, arg1: Doc) => void; + update: (arg0: Uint8Array, arg1: any, arg2: Doc, arg3: Transaction) => void; + updateV2: ( + arg0: Uint8Array, + arg1: any, + arg2: Doc, + arg3: Transaction, + ) => void; + beforeAllTransactions: (arg0: Doc) => void; + beforeTransaction: (arg0: Transaction, arg1: Doc) => void; + beforeObserverCalls: (arg0: Transaction, arg1: Doc) => void; + afterTransaction: (arg0: Transaction, arg1: Doc) => void; + afterTransactionCleanup: (arg0: Transaction, arg1: Doc) => void; + afterAllTransactions: (arg0: Doc, arg1: Array) => void; + subdocs: ( + arg0: { + loaded: Set; + added: Set; + removed: Set; + }, + arg1: Doc, + arg2: Transaction, + ) => void; + }; /** - * An YXmlElement imitates the behavior of a - * https://developer.mozilla.org/en-US/docs/Web/API/Element|Dom Element + * A transaction is created for every change on the Yjs model. It is possible + * to bundle changes on the Yjs model in a single transaction to + * minimize the number on messages sent and the number of observer calls. + * If possible the user of this library should bundle as many changes as + * possible. Here is an example to illustrate the advantages of bundling: * - * * An YXmlElement has attributes (key value pairs) - * * An YXmlElement has childElements that must inherit from YXmlElement - * - * @template {{ [key: string]: ValueTypes }} [KV={ [key: string]: string }] - */ - export class YXmlElement< - KV extends { - [key: string]: ValueTypes; - } = { - [key: string]: string; - }, - > extends YXmlFragment { - constructor(nodeName?: string); - nodeName: string; - get nextSibling(): - | YXmlElement<{ - [key: string]: string; - }> - | YXmlText - | null; - get prevSibling(): - | YXmlElement<{ - [key: string]: string; - }> - | YXmlText - | null; + * @example + * const ydoc = new Y.Doc() + * const map = ydoc.getMap('map') + * // Log content when change is triggered + * map.observe(() => { + * console.log('change triggered') + * }) + * // Each change on the map type triggers a log message: + * 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: + * ydoc.transact(() => { + * map.set('a', 1) + * map.set('b', 1) + * }) // => "change triggered" + */ + export class Transaction { + constructor(doc: Doc, origin: any, local: boolean); + /** + * The Yjs instance. + */ + doc: Doc; /** - * 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. + * Describes the set of deleted items by ids */ - clone(): YXmlElement; + deleteSet: Struct.DeleteSet; /** - * Removes an attribute from this YXmlElement. - * - * @param {string} attributeName The attribute name that is to be removed. - * - * @public + * Holds the state before the transaction started. */ - removeAttribute(attributeName: string): void; + beforeState: Map; /** - * Sets or updates an attribute. - * - * @template {keyof KV & string} KEY - * - * @param {KEY} attributeName The attribute name that is to be set. - * @param {KV[KEY]} attributeValue The attribute value that is to be set. + * Holds the state after the transaction. + */ + afterState: Map; + /** + * 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) + */ + changed: Map>, Set>; + /** + * Stores the events for the types that observe also child elements. + * It is mainly used by `observeDeep`. + */ + changedParentTypes: Map< + Type.AbstractType>, + Array> + >; + origin: any; + /** + * Stores meta information on the transaction + */ + meta: Map; + /** + * Whether this change originates from this doc. + */ + local: boolean; + subdocsAdded: Set; + subdocsRemoved: Set; + subdocsLoaded: Set; + } + + export function tryGc( + ds: Struct.DeleteSet, + store: Struct.StructStore, + gcFilter: (arg0: Struct.Item) => boolean, + ): void; + export function transact( + doc: Doc, + f: (arg0: Transaction) => T, + origin?: any, + local?: boolean, + ): T; + + module Struct { + class DeleteItem { + constructor(clock: number, len: number); + clock: number; + len: number; + } + /** + * 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. */ - setAttribute( - attributeName: KEY, - attributeValue: KV[KEY], + class DeleteSet { + clients: Map>; + } + export function iterateDeletedStructs( + transaction: Transaction, + ds: DeleteSet, + f: (arg0: Struct.GC | Struct.Item) => void, ): void; + export function isDeleted(ds: DeleteSet, id: Struct.ID): boolean; + export function mergeDeleteSets(dss: Array): DeleteSet; + export function createDeleteSet(): DeleteSet; + export function createDeleteSetFromStructStore( + ss: Struct.StructStore, + ): DeleteSet; + export function equalDeleteSets(ds1: DeleteSet, ds2: DeleteSet): boolean; + + export class ID { + /** + * @param {number} client client id + * @param {number} clock unique per client id, continuous number + */ + constructor(client: number, clock: number); + /** + * Client id + */ + client: number; + /** + * unique per client id, continuous number + */ + clock: number; + } + export function compareIDs(a: ID | null, b: ID | null): boolean; + export function createID(client: number, clock: number): ID; + export function findRootTypeKey(type: Type.AbstractType): string; + + export function isParentOf( + parent: Type.AbstractType, + child: Struct.Item | null, + ): boolean; + + export function logType(type: Type.AbstractType): void; + + export class StructStore { + clients: Map>; + pendingStructs: { + missing: Map; + update: Uint8Array; + } | null; + pendingDs: null | Uint8Array; + } + export function getState(store: StructStore, client: number): number; + export function findIndexSS( + structs: Array, + clock: number, + ): number; + export function getItem(arg0: StructStore, arg1: ID): Struct.Item; + + export class AbstractStruct { + constructor(id: Struct.ID, length: number); + id: Struct.ID; + length: number; + get deleted(): boolean; + /** + * Merge this struct with the item to the right. + * This method is already assuming that `this.id.clock + this.length === this.id.clock`. + * Also this method does *not* remove right from StructStore! + * @return {boolean} wether this merged with right + */ + mergeWith(right: AbstractStruct): boolean; + write( + encoder: Codec.UpdateEncoderV1 | Codec.UpdateEncoderV2, + offset: number, + encodingRef: number, + ): void; + integrate(transaction: Transaction, offset: number): void; + } + + export class GC extends AbstractStruct { + delete(): void; + mergeWith(right: GC): boolean; + write( + encoder: Codec.UpdateEncoderV1 | Codec.UpdateEncoderV2, + offset: number, + ): void; + getMissing( + transaction: Transaction, + store: Struct.StructStore, + ): null | number; + } + /** - * Returns an attribute value that belongs to the attribute name. - * - * @template {keyof KV & string} KEY + * Abstract class that represents any content. + */ + export class Item extends AbstractStruct { + /** + * @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. + */ + constructor( + id: Struct.ID, + left: Item | null, + origin: Struct.ID | null, + right: Item | null, + rightOrigin: Struct.ID | null, + parent: Type.AbstractType | Struct.ID | null, + parentSub: string | null, + content: AbstractContent, + ); + /** + * The item that was originally to the left of this item. + */ + origin: Struct.ID | null; + /** + * The item that is currently to the left of this item. + */ + left: Item | null; + /** + * The item that is currently to the right of this item. + */ + right: Item | null; + /** + * The item that was originally to the right of this item. + */ + rightOrigin: Struct.ID | null; + parent: Type.AbstractType | Struct.ID | null; + /** + * If the parent refers to this item with some kind of key (e.g. YMap, the + * key is specified here. The key is then used to refer to the list in which + * to insert this item. If `parentSub = null` type._start is the list in + * which to insert to. Otherwise it is `parent._map`. + */ + parentSub: string | null; + /** + * If this type's effect is redone this type refers to the type that undid + * this operation. + */ + redone: Struct.ID | null; + content: AbstractContent; + /** + * bit1: keep + * bit2: countable + * bit3: deleted + * bit4: mark - mark node as fast-search-marker + */ + info: number; + /** + * This is used to mark the item as an indexed fast-search marker + */ + set marker(arg: boolean); + get marker(): boolean; + set keep(arg: boolean); + /** + * If true, do not garbage collect this Item. + */ + get keep(): boolean; + get countable(): boolean; + set deleted(arg: boolean); + /** + * Whether this item was deleted or not. + */ + get deleted(): boolean; + markDeleted(): void; + /** + * Return the creator clientID of the missing op or define missing items and return null. + */ + getMissing( + transaction: Transaction, + store: Struct.StructStore, + ): null | number; + /** + * Returns the next non-deleted item + */ + get next(): Item | null; + /** + * Returns the previous non-deleted item + */ + get prev(): Item | null; + /** + * Computes the last content address of this Item. + */ + get lastId(): Struct.ID; + /** + * Try to merge two items + */ + mergeWith(right: Item): boolean; + /** + * Mark this Item as deleted. + */ + delete(transaction: Transaction): void; + gc(store: Struct.StructStore, parentGCd: boolean): void; + /** + * Transform the properties of this type to binary and write it to an + * BinaryEncoder. + * + * This is called when this Item is sent to a remote peer. + */ + write( + encoder: Codec.UpdateEncoderV1 | Codec.UpdateEncoderV2, + offset: number, + ): void; + } + + export class AbstractContent { + getLength(): number; + getContent(): Array; + /** + * Should return false if this Item is some kind of meta information + * (e.g. format information). + * + * * Whether this Item should be addressable via `yarray.get(i)` + * * Whether this Item should be counted when computing yarray.length + */ + isCountable(): boolean; + copy(): AbstractContent; + splice(_offset: number): AbstractContent; + mergeWith(_right: AbstractContent): boolean; + integrate(_transaction: Transaction, _item: Item): void; + delete(_transaction: Transaction): void; + gc(_store: Struct.StructStore): void; + write( + _encoder: Codec.UpdateEncoderV1 | Codec.UpdateEncoderV2, + _offset: number, + ): void; + getRef(): number; + } + + export class Skip extends AbstractStruct { + delete(): void; + mergeWith(right: Skip): boolean; + write( + encoder: Codec.UpdateEncoderV1 | Codec.UpdateEncoderV2, + offset: number, + ): void; + getMissing( + transaction: Transaction, + store: Struct.StructStore, + ): null | number; + } + } + + module Type { + module Event { + /** + * YEvent describes the changes on a YType. + */ + export class YEvent> { + /** + * @param {T} target The changed type. + * @param {Transaction} transaction + */ + constructor(target: T, transaction: Transaction); + /** + * The type on which this event was created on. + */ + target: T; + /** + * The current target on which the observe callback is called. + */ + currentTarget: AbstractType; + /** + * The transaction that triggered this event. + */ + transaction: Transaction; + /** + * Computes the path from `y` to the changed type. + * + * @todo v14 should standardize on path: Array<{parent, index}> because that is easier to work with. + * + * The following property holds: + * @example + * let type = y + * event.path.forEach(dir => { + * type = type.get(dir) + * }) + * type === event.target // => true + */ + get path(): (string | number)[]; + /** + * Check if a struct is deleted by this event. + * + * In contrast to change.deleted, this method also returns true if the struct was added and then deleted. + */ + deletes(struct: Struct.AbstractStruct): boolean; + get keys(): Map< + string, + { + action: "add" | "update" | "delete"; + oldValue: any; + newValue: any; + } + >; + /** + * This is a computed property. Note that this can only be safely computed during the + * event call. Computing this property after other changes happened might result in + * unexpected behavior (incorrect computation of deltas). A safe way to collect changes + * is to store the `changes` or the `delta` object. Avoid storing the `transaction` object. + */ + get delta(): { + insert?: string | object | any[] | AbstractType | undefined; + retain?: number | undefined; + delete?: number | undefined; + attributes?: + | { + [x: string]: any; + } + | undefined; + }[]; + /** + * 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. + */ + adds(struct: Struct.AbstractStruct): boolean; + /** + * This is a computed property. Note that this can only be safely computed during the + * event call. Computing this property after other changes happened might result in + * unexpected behavior (incorrect computation of deltas). A safe way to collect changes + * is to store the `changes` or the `delta` object. Avoid storing the `transaction` object. + */ + get changes(): { + added: Set; + deleted: Set; + keys: Map< + string, + { + action: "add" | "update" | "delete"; + oldValue: any; + } + >; + delta: Array<{ + insert?: Array | string; + delete?: number; + retain?: number; + }>; + }; + } + + /** + * Event that describes the changes on a YArray + */ + export class YArrayEvent extends YEvent> { + constructor(target: YArray, transaction: Transaction); + } + + /** + * 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: YMap, transaction: Transaction, subs: Set); + keysChanged: Set; + } + + /** + * 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: YText, transaction: Transaction, subs: Set); + /** + * Set of all changed attributes. + */ + keysChanged: Set; + /** + * Compute the changes in the delta format. + * A {@link https://quilljs.com/docs/delta/|Quill Delta}) that represents the changes on the document. + */ + get delta(): { + insert?: string | object | AbstractType | undefined; + delete?: number | undefined; + retain?: number | undefined; + attributes?: + | { + [x: string]: any; + } + | undefined; + }[]; + } + } + + export function getTypeChildren(t: AbstractType): Array; + + /** + * Abstract Yjs Type class + */ + export class AbstractType { + doc: Doc | null; + get parent(): AbstractType | null; + + /** + * 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. + */ + clone(): AbstractType; + /** + * Observe all events that are created on this type. + */ + observe(f: (arg0: EventType, arg1: Transaction) => void): void; + /** + * Observe all events that are created by this type and its children. + */ + observeDeep( + f: (arg0: Array>, arg1: Transaction) => void, + ): void; + /** + * Unregister an observer function. + */ + unobserve(f: (arg0: EventType, arg1: Transaction) => void): void; + /** + * Unregister an observer function. + */ + unobserveDeep( + f: (arg0: Array>, arg1: Transaction) => void, + ): void; + toJSON(): any; + } + + /** + * A shared Array implementation. + */ + export class YArray + extends AbstractType> + implements Iterable + { + /** + * Construct a new YArray containing the specified items. + * @template {Object|Array|number|null|string|Uint8Array} T + * @param {Array} items + * @return {YArray} + */ + static from< + T extends + | string + | number + | any[] + | Uint8Array + | { + [x: string]: any; + } + | null, + >(items: T[]): 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(): YArray; + get length(): number; + /** + * Inserts new content at an index. + * + * Important: This function expects an array of content. Not just a content + * object. The reason for this "weirdness" is that inserting several elements + * is very efficient when it is done as a single operation. + * + * @example + * // Insert character 'a' at position 0 + * yarray.insert(0, ['a']) + * // Insert numbers 1, 2 at position 1 + * yarray.insert(1, [1, 2]) + * + * @param {number} index The index to insert content at. + * @param {Array} content The array of content + */ + insert(index: number, content: Array): void; + /** + * Appends content to this YArray. + * + * @param {Array} content Array of content to append. + * + * @todo Use the following implementation in all types. + */ + push(content: Array): void; + /** + * Prepends content to this YArray. + * + * @param {Array} content Array of content to prepend. + */ + unshift(content: Array): void; + /** + * Deletes elements starting from an index. + * + * @param {number} index Index at which to start deleting elements + * @param {number} length The number of elements to remove. Defaults to 1. + */ + delete(index: number, length?: number): void; + /** + * Returns the i-th element from a YArray. + * + * @param {number} index The index of the element to return from the YArray + * @return {T} + */ + get(index: number): T; + /** + * Transforms this YArray to a JavaScript Array. + * + * @return {Array} + */ + toArray(): Array; + /** + * Returns a portion of this YArray into a JavaScript Array selected + * from start to end (end not included). + * + * @param {number} [start] + * @param {number} [end] + * @return {Array} + */ + slice(start?: number | undefined, end?: number | undefined): Array; + /** + * Transforms this Shared Type to a JSON object. + * + * @return {Array} + */ + toJSON(): Array; + /** + * Returns an Array with the result of calling a provided function on every + * element of this YArray. + * + * @template M + * @param {function(T,number,YArray):M} f Function that produces an element of the new Array + * @return {Array} A new array with each element being the result of the + * callback function + */ + map(f: (arg0: T, arg1: number, arg2: YArray) => M): M[]; + /** + * 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. + */ + forEach(f: (arg0: T, arg1: number, arg2: YArray) => void): void; + /** + * @return {IterableIterator} + */ + [Symbol.iterator](): IterableIterator; + } + + /** + * A shared Map implementation. + */ + export class YMap + extends AbstractType> + implements Iterable<[string, MapType]> + { + /** + * + * @param {Iterable=} entries - an optional iterable to initialize the YMap + */ + constructor(entries?: Iterable | undefined); + /** + * 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. + */ + clone(): YMap; + /** + * Transforms this Shared Type to a JSON object. + */ + toJSON(): { + [x: string]: any; + }; + /** + * Returns the size of the YMap (count of key/value pairs) + */ + get size(): number; + /** + * Returns the keys for each element in the YMap Type. + */ + keys(): IterableIterator; + /** + * Returns the values for each element in the YMap Type. + */ + values(): IterableIterator; + /** + * Returns an Iterator of [key, value] pairs + */ + entries(): IterableIterator<[string, MapType]>; + /** + * Executes a provided function on once on every key-value pair. + * + * @param {function(MapType,string,YMap):void} f A function to execute on every element of this YArray. + */ + forEach( + f: (arg0: MapType, arg1: string, arg2: YMap) => void, + ): void; + /** + * Remove a specified element from this YMap. + * + * @param {string} key The key of the element to remove. + */ + delete(key: string): void; + /** + * 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 {VAL} value The value of the element to add + * @return {VAL} + */ + set(key: string, value: VAL): VAL; + /** + * Returns a specified element from this YMap. + * + * @param {string} key + * @return {MapType|undefined} + */ + get(key: string): MapType | undefined; + /** + * Returns a boolean indicating whether the specified key exists or not. + * + * @param {string} key The key to test. + * @return {boolean} + */ + has(key: string): boolean; + /** + * Removes all elements from this YMap. + */ + clear(): void; + /** + * Returns an Iterator of [key, value] pairs + * + * @return {IterableIterator<[string, MapType]>} + */ + [Symbol.iterator](): IterableIterator<[string, MapType]>; + } + + export function cleanupYTextFormatting(type: YText): number; + + /** + * Type that represents text with formatting information. * - * @param {KEY} attributeName The attribute name that identifies the - * queried value. - * @return {KV[KEY]|undefined} The queried attribute value. + * This type replaces y-richtext as this implementation is able to handle + * block formats (format information on a paragraph), embeds (complex elements + * like pictures and videos), and text formats (**bold**, *italic*). + */ + export class YText extends AbstractType { + /** + * @param {String} [string] The initial value of the YText. + */ + constructor(string?: string | undefined); + /** + * Number of characters of this text type. + */ + get length(): number; + /** + * 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. + */ + clone(): YText; + /** + * Returns the unformatted string representation of this YText type. + */ + toJSON(): string; + /** + * 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. + */ + applyDelta(delta: any, opts?: { sanitize?: boolean | undefined }): void; + /** + * Returns the Delta representation of this YText type. + */ + toDelta( + snapshot?: Snapshot.Snapshot | undefined, + prevSnapshot?: Snapshot.Snapshot | undefined, + computeYChange?: + | ((arg0: "removed" | "added", arg1: Struct.ID) => any) + | undefined, + ): any; + /** + * Insert text at a given index. + * + * @param {number} index The index at which to start inserting. + * @param {String} text The text to insert at the specified position. + * @param {TextAttributes} [attributes] Optionally define some formatting + * information to apply on the inserted + * Text. + * @public + */ + insert( + index: number, + text: string, + attributes?: Object | undefined, + ): void; + /** + * Inserts an embed at a index. + * + * @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 + * embed + */ + insertEmbed( + index: number, + embed: Object | AbstractType, + attributes?: Object | undefined, + ): void; + /** + * Deletes text starting from an index. + * + * @param {number} index Index at which to start deleting. + * @param {number} length The number of characters to remove. Defaults to 1. + * + * @public + */ + delete(index: number, length: number): void; + /** + * Assigns properties to a range of text. + * + * @param {number} index The position where to start formatting. + * @param {number} length The amount of characters to assign properties to. + * @param {TextAttributes} attributes Attribute information to apply on the + * text. + */ + format(index: number, length: number, attributes: TextAttributes): void; + /** + * Removes an attribute. + * + * @note Xml-Text nodes don't have attributes. You can use this feature to assign properties to complete text-blocks. + * + * @param {String} attributeName The attribute name that is to be removed. + */ + removeAttribute(attributeName: string): void; + /** + * Sets or updates an attribute. + * + * @note Xml-Text nodes don't have attributes. You can use this feature to assign properties to complete text-blocks. + * + * @param {String} attributeName The attribute name that is to be set. + * @param {any} attributeValue The attribute value that is to be set. + */ + setAttribute(attributeName: string, attributeValue: any): void; + /** + * Returns an attribute value that belongs to the attribute name. + * + * @note Xml-Text nodes don't have attributes. You can use this feature to assign properties to complete text-blocks. + * + * @param {String} attributeName The attribute name that identifies the + * queried value. + * @return {any} The queried attribute value. + */ + getAttribute(attributeName: string): any; + /** + * Returns all attribute name/value pairs in a JSON Object. + * + * @note Xml-Text nodes don't have attributes. You can use this feature to assign properties to complete text-blocks. + * + * @return {Object} A JSON Object that describes the attributes. + */ + getAttributes(): { + [x: string]: any; + }; + } + /** + * Attributes that can be assigned to a selection of text. */ - getAttribute( - attributeName: KEY_1, - ): KV[KEY_1] | undefined; + export type TextAttributes = Object; + + module YXml { + /** + * Represents a subset of the nodes of a YXmlElement / YXmlFragment and a + * position within them. + * + * Can be created with {@link YXmlFragment#createTreeWalker} + */ + export class YXmlTreeWalker + implements Iterable + { + constructor( + root: YXmlFragment | YXmlElement, + f?: ((arg0: AbstractType) => boolean) | undefined, + ); + /** + * Get the next node. + */ + next(): IteratorResult; + [Symbol.iterator](): YXmlTreeWalker; + } + /** + * Represents a list of {@link YXmlElement}.and {@link YXmlText} types. + * A YxmlFragment is similar to a {@link YXmlElement}, but it does not have a + * nodeName and it does not have attributes. Though it can be bound to a DOM + * element - in this case the attributes and the nodeName are not shared. + */ + export class YXmlFragment extends AbstractType { + constructor(); + get firstChild(): + | YXmlElement<{ + [key: string]: string; + }> + | YXmlText + | null; + /** + * 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. + */ + clone(): YXmlFragment; + get length(): number; + /** + * 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. + */ + createTreeWalker( + filter: (arg0: AbstractType) => boolean, + ): YXmlTreeWalker; + /** + * 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. + */ + querySelector( + query: CSS_Selector, + ): YXmlElement | YXmlText | YXmlHook | null; + /** + * 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. + */ + querySelectorAll( + query: CSS_Selector, + ): Array; + toJSON(): string; + /** + * 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} + */ + toDOM( + _document?: Document | undefined, + hooks?: + | { + [x: string]: any; + } + | undefined, + binding?: any, + ): Node; + /** + * Inserts new content at an index. + * + * @example + * // Insert character 'a' at position 0 + * xml.insert(0, [new Y.XmlText('text')]) + * + * @param {number} index The index to insert content at + * @param {Array} content The array of content + */ + insert(index: number, content: Array): void; + /** + * Inserts new content at an index. + * + * @example + * // Insert character 'a' at position 0 + * xml.insert(0, [new Y.XmlText('text')]) + * + * @param {null|Item|YXmlElement|YXmlText} ref The index to insert content at + * @param {Array} content The array of content + */ + insertAfter( + ref: null | Struct.Item | YXmlElement | YXmlText, + content: Array, + ): void; + /** + * Deletes elements starting from an index. + * + * @param {number} index Index at which to start deleting elements + * @param {number} [length=1] The number of elements to remove. Defaults to 1. + */ + delete(index: number, length?: number | undefined): void; + /** + * Transforms this YArray to a JavaScript Array. + * + * @return {Array} + */ + toArray(): Array; + /** + * Appends content to this YArray. + * + * @param {Array} content Array of content to append. + */ + push(content: Array): void; + /** + * Prepends content to this YArray. + * + * @param {Array} content Array of content to prepend. + */ + unshift(content: Array): void; + /** + * Returns the i-th element from a YArray. + * + * @param {number} index The index of the element to return from the YArray + * @return {YXmlElement|YXmlText} + */ + get(index: number): YXmlElement | YXmlText; + /** + * Returns a portion of this YXmlFragment into a JavaScript Array selected + * from start to end (end not included). + * + * @param {number} [start] + * @param {number} [end] + * @return {Array} + */ + slice( + start?: number | undefined, + end?: number | undefined, + ): Array; + /** + * Executes a provided function on once on every child element. + * + * @param {function(YXmlElement|YXmlText,number, typeof self):void} f A function to execute on every element of this YArray. + */ + forEach( + f: ( + arg0: YXmlElement | YXmlText, + arg1: number, + arg2: typeof self, + ) => void, + ): void; + } + + /** + * Define the elements to which a set of CSS queries apply. + * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors|CSS_Selectors} + */ + type CSS_Selector = string; + + /** + * An YXmlElement imitates the behavior of a + * 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]: ValueTypes }} [KV={ [key: string]: string }] + */ + export class YXmlElement< + KV extends { + [key: string]: ValueTypes; + } = { + [key: string]: string; + }, + > extends YXmlFragment { + constructor(nodeName?: string); + nodeName: string; + get nextSibling(): + | YXmlElement<{ + [key: string]: string; + }> + | YXmlText + | null; + get prevSibling(): + | YXmlElement<{ + [key: string]: string; + }> + | YXmlText + | null; + /** + * 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. + */ + clone(): YXmlElement; + /** + * Removes an attribute from this YXmlElement. + * + * @param {string} attributeName The attribute name that is to be removed. + * + * @public + */ + removeAttribute(attributeName: string): void; + /** + * Sets or updates an attribute. + * + * @template {keyof KV & string} KEY + * + * @param {KEY} attributeName The attribute name that is to be set. + * @param {KV[KEY]} attributeValue The attribute value that is to be set. + */ + setAttribute( + attributeName: KEY, + attributeValue: KV[KEY], + ): void; + /** + * Returns an attribute value that belongs to the attribute name. + * + * @template {keyof KV & string} KEY + * + * @param {KEY} attributeName The attribute name that identifies the + * queried value. + * @return {KV[KEY]|undefined} The queried attribute value. + */ + getAttribute( + attributeName: KEY_1, + ): KV[KEY_1] | undefined; + /** + * Returns whether an attribute exists + * + * @param {string} attributeName The attribute name to check for existence. + * @return {boolean} whether the attribute exists. + */ + hasAttribute(attributeName: string): boolean; + /** + * Returns all attribute name/value pairs in a JSON Object. + * + * @param {Snapshot} [snapshot] + * @return {{ [Key in Extract]?: KV[Key]}} A JSON Object that describes the attributes. + */ + getAttributes(snapshot?: Snapshot.Snapshot | undefined): { + [Key in Extract]?: KV[Key] | undefined; + }; + } + + type ValueTypes = + | Object + | number + | null + | Array + | string + | Uint8Array + | AbstractType; + + /** + * An Event that describes changes on a YXml Element or Yxml Fragment + */ + export class YXmlEvent extends Event.YEvent< + | YXmlElement<{ + [key: string]: string; + }> + | YXmlFragment + | YXmlText + > { + /** + * @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: YXmlElement | YXmlText | YXmlFragment, + subs: Set, + transaction: Transaction, + ); + /** + * Set of all changed attributes. + * @type {Set} + */ + attributesChanged: Set; + } + + /** + * You can manage binding to a custom type with YXmlHook. + */ + export class YXmlHook extends YMap { + /** + * @param {string} hookName nodeName of the Dom Node. + */ + constructor(hookName: string); + hookName: string; + /** + * 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. + */ + clone(): YXmlHook; + /** + * 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} + */ + toDOM( + _document?: Document | undefined, + hooks?: + | { + [x: string]: any; + } + | undefined, + binding?: any, + ): Element; + } + + /** + * Represents text in a Dom Element. In the future this type will also handle + * simple formatting information like bold and italic. + */ + export class YXmlText extends YText { + get nextSibling(): + | YXmlElement<{ + [key: string]: string; + }> + | YXmlText + | null; + get prevSibling(): + | YXmlElement<{ + [key: string]: string; + }> + | YXmlText + | null; + /** + * 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. + */ + clone(): YXmlText; + /** + * Creates a Dom Element that mirrors this YXmlText. + * + * @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} + */ + toDOM( + _document?: Document | undefined, + hooks?: + | { + [x: string]: any; + } + | undefined, + binding?: any, + ): Text; + toString(): any; + } + } + } + + module Codec { + export class DSDecoderV1 { + constructor(decoder: decoding.Decoder); + restDecoder: decoding.Decoder; + resetDsCurVal(): void; + readDsClock(): number; + readDsLen(): number; + } + export class UpdateDecoderV1 extends DSDecoderV1 { + readLeftID(): Struct.ID; + readRightID(): Struct.ID; + /** + * Read the next client id. + * Use this in favor of readID whenever possible to reduce the number of objects created. + */ + readClient(): number; + readInfo(): number; + readString(): string; + /** + * @return {boolean} isKey + */ + readParentInfo(): boolean; + readTypeRef(): number; + /** + * Write len of a struct - well suited for Opt RLE encoder. + */ + readLen(): number; + readAny(): any; + readBuf(): Uint8Array; + /** + * Legacy implementation uses JSON parse. We use any-decoding in v2. + */ + readJSON(): any; + readKey(): string; + } + export class DSDecoderV2 { + constructor(decoder: decoding.Decoder); + restDecoder: decoding.Decoder; + resetDsCurVal(): void; + readDsClock(): number; + readDsLen(): number; + } + export class UpdateDecoderV2 extends DSDecoderV2 { + /** + * List of cached keys. If the keys[id] does not exist, we read a new key + * from stringEncoder and push it to keys. + */ + keys: Array; + keyClockDecoder: decoding.IntDiffOptRleDecoder; + clientDecoder: decoding.UintOptRleDecoder; + leftClockDecoder: decoding.IntDiffOptRleDecoder; + rightClockDecoder: decoding.IntDiffOptRleDecoder; + infoDecoder: decoding.RleDecoder; + stringDecoder: decoding.StringDecoder; + parentInfoDecoder: decoding.RleDecoder; + typeRefDecoder: decoding.UintOptRleDecoder; + lenDecoder: decoding.UintOptRleDecoder; + readLeftID(): Struct.ID; + readRightID(): Struct.ID; + /** + * Read the next client id. + * Use this in favor of readID whenever possible to reduce the number of objects created. + */ + readClient(): number; + readInfo(): number; + readString(): string; + readParentInfo(): boolean; + readTypeRef(): number; + /** + * Write len of a struct - well suited for Opt RLE encoder. + */ + readLen(): number; + readAny(): any; + readBuf(): Uint8Array; + /** + * This is mainly here for legacy purposes. + * + * Initial we incoded objects using JSON. Now we use the much faster lib0/any-encoder. This method mainly exists for legacy purposes for the v1 encoder. + */ + readJSON(): any; + readKey(): string; + } + export class DSEncoderV1 { + restEncoder: encoding.Encoder; + toUint8Array(): Uint8Array; + resetDsCurVal(): void; + writeDsClock(clock: number): void; + writeDsLen(len: number): void; + } + export class UpdateEncoderV1 extends DSEncoderV1 { + writeLeftID(id: Struct.ID): void; + writeRightID(id: Struct.ID): void; + /** + * Use writeClient and writeClock instead of writeID if possible. + */ + writeClient(client: number): void; + writeInfo(info: number): void; + writeString(s: string): void; + writeParentInfo(isYKey: boolean): void; + writeTypeRef(info: number): void; + /** + * Write len of a struct - well suited for Opt RLE encoder. + */ + writeLen(len: number): void; + writeAny(any: any): void; + writeBuf(buf: Uint8Array): void; + writeJSON(embed: any): void; + writeKey(key: string): void; + } + export class DSEncoderV2 { + restEncoder: encoding.Encoder; + dsCurrVal: number; + toUint8Array(): Uint8Array; + resetDsCurVal(): void; + /** + * @param {number} clock + */ + writeDsClock(clock: number): void; + /** + * @param {number} len + */ + writeDsLen(len: number): void; + } + export class UpdateEncoderV2 extends DSEncoderV2 { + /** + * @type {Map} + */ + keyMap: Map; + /** + * Refers to the next uniqe key-identifier to me used. + * See writeKey method for more information. + * + * @type {number} + */ + keyClock: number; + keyClockEncoder: encoding.IntDiffOptRleEncoder; + clientEncoder: encoding.UintOptRleEncoder; + leftClockEncoder: encoding.IntDiffOptRleEncoder; + rightClockEncoder: encoding.IntDiffOptRleEncoder; + infoEncoder: encoding.RleEncoder; + stringEncoder: encoding.StringEncoder; + parentInfoEncoder: encoding.RleEncoder; + typeRefEncoder: encoding.UintOptRleEncoder; + lenEncoder: encoding.UintOptRleEncoder; + writeLeftID(id: Struct.ID): void; + writeRightID(id: Struct.ID): void; + writeClient(client: number): void; + writeInfo(info: number): void; + writeString(s: string): void; + writeParentInfo(isYKey: boolean): void; + writeTypeRef(info: number): void; + /** + * Write len of a struct - well suited for Opt RLE encoder. + */ + writeLen(len: number): void; + writeAny(any: any): void; + writeBuf(buf: Uint8Array): void; + /** + * This is mainly here for legacy purposes. + * + * Initial we incoded objects using JSON. Now we use the much faster lib0/any-encoder. This method mainly exists for legacy purposes for the v1 encoder. + */ + writeJSON(embed: any): void; + /** + * Property keys are often reused. For example, in y-prosemirror the key `bold` might + * occur very often. For a 3d application, the key `position` might occur very often. + * + * We cache these keys in a Map and refer to them via a unique number. + */ + writeKey(key: string): void; + } + } + + module Update { + export function readUpdateV2( + decoder: decoding.Decoder, + ydoc: Doc, + transactionOrigin?: any, + structDecoder?: Codec.UpdateDecoderV1 | Codec.UpdateDecoderV2 | undefined, + ): void; + export function readUpdate( + decoder: decoding.Decoder, + ydoc: Doc, + transactionOrigin?: any, + ): void; + export function applyUpdateV2( + ydoc: Doc, + update: Uint8Array, + transactionOrigin?: any, + YDecoder?: + | typeof Codec.UpdateDecoderV1 + | typeof Codec.UpdateDecoderV2 + | undefined, + ): void; + export function applyUpdate( + ydoc: Doc, + update: Uint8Array, + transactionOrigin?: any, + ): void; + export function encodeStateAsUpdateV2( + doc: Doc, + encodedTargetStateVector?: Uint8Array | undefined, + encoder?: Codec.UpdateEncoderV2 | Codec.UpdateEncoderV1 | undefined, + ): Uint8Array; + export function encodeStateAsUpdate( + doc: Doc, + encodedTargetStateVector?: Uint8Array | undefined, + ): Uint8Array; + export function decodeStateVector( + decodedState: Uint8Array, + ): Map; + export function encodeStateVectorV2( + doc: Doc | Map, + encoder?: Codec.DSEncoderV1 | Codec.DSEncoderV2 | undefined, + ): Uint8Array; + export function encodeStateVector( + doc: Doc | Map, + ): Uint8Array; + + export function logUpdate(update: Uint8Array): void; + export function logUpdateV2( + update: Uint8Array, + YDecoder?: + | typeof Codec.UpdateDecoderV1 + | typeof Codec.UpdateDecoderV2 + | undefined, + ): void; + export function decodeUpdate(update: Uint8Array): { + structs: (Struct.GC | Struct.Item | Struct.Skip)[]; + ds: Struct.DeleteSet; + }; + export function decodeUpdateV2( + update: Uint8Array, + YDecoder?: + | typeof Codec.UpdateDecoderV1 + | typeof Codec.UpdateDecoderV2 + | undefined, + ): { + structs: (Struct.GC | Struct.Item | Struct.Skip)[]; + ds: Struct.DeleteSet; + }; + + export function mergeUpdates(updates: Array): Uint8Array; + export function encodeStateVectorFromUpdateV2( + update: Uint8Array, + YEncoder?: typeof Codec.DSEncoderV1 | typeof Codec.DSEncoderV2, + YDecoder?: typeof Codec.UpdateDecoderV1 | typeof Codec.UpdateDecoderV2, + ): Uint8Array; + export function encodeStateVectorFromUpdate(update: Uint8Array): Uint8Array; + export function parseUpdateMetaV2( + update: Uint8Array, + YDecoder?: typeof Codec.UpdateDecoderV1 | typeof Codec.UpdateDecoderV2, + ): { + from: Map; + to: Map; + }; + export function parseUpdateMeta(update: Uint8Array): { + from: Map; + to: Map; + }; + export function mergeUpdatesV2( + updates: Array, + YDecoder?: + | typeof Codec.UpdateDecoderV1 + | typeof Codec.UpdateDecoderV2 + | undefined, + YEncoder?: + | typeof Codec.UpdateEncoderV2 + | typeof Codec.UpdateEncoderV1 + | undefined, + ): Uint8Array; + export function diffUpdateV2( + update: Uint8Array, + sv: Uint8Array, + YDecoder?: + | typeof Codec.UpdateDecoderV1 + | typeof Codec.UpdateDecoderV2 + | undefined, + YEncoder?: + | typeof Codec.UpdateEncoderV2 + | typeof Codec.UpdateEncoderV1 + | undefined, + ): Uint8Array; + export function diffUpdate(update: Uint8Array, sv: Uint8Array): Uint8Array; + export function obfuscateUpdate( + update: Uint8Array, + opts?: ObfuscatorOptions | undefined, + ): Uint8Array; + export function obfuscateUpdateV2( + update: Uint8Array, + opts?: ObfuscatorOptions | undefined, + ): Uint8Array; + export function convertUpdateFormatV1ToV2(update: Uint8Array): Uint8Array; + export function convertUpdateFormatV2ToV1(update: Uint8Array): Uint8Array; + type ObfuscatorOptions = { + formatting?: boolean | undefined; + subdocs?: boolean | undefined; + /** + * Whether to obfuscate nodeName / hookName + */ + yxml?: boolean | undefined; + }; + } +} + +declare module RelativePosition { + /** + * A relative position is based on the Yjs model and is not affected by document changes. + * E.g. If you place a relative position before a certain character, it will always point to this character. + * If you place a relative position at the end of a type, it will always point to the end of the type. + * + * A numeric position is often unsuited for user selections, because it does not change when content is inserted + * before or after. + * + * ```Insert(0, 'x')('a|bc') = 'xa|bc'``` Where | is the relative position. + * + * One of the properties must be defined. + * + * @example + * // Current cursor position is at position 10 + * const relativePosition = createRelativePositionFromIndex(yText, 10) + * // modify yText + * yText.insert(0, 'abc') + * yText.delete(3, 10) + * // Compute the cursor position + * const absolutePosition = createAbsolutePositionFromRelativePosition(y, relativePosition) + * absolutePosition.type === yText // => true + * console.log('cursor location is ' + absolutePosition.index) // => cursor location is 3 + * + */ + export class RelativePosition { + constructor( + type: Y.Struct.ID | null, + tname: string | null, + item: Y.Struct.ID | null, + assoc?: number, + ); + type: Y.Struct.ID | null; + tname: string | null; + item: Y.Struct.ID | null; /** - * Returns whether an attribute exists + * A relative position is associated to a specific character. By default + * assoc >= 0, the relative position is associated to the character + * after the meant position. + * I.e. position 1 in 'ab' is associated to character 'b'. * - * @param {string} attributeName The attribute name to check for existence. - * @return {boolean} whether the attribute exists. + * If assoc < 0, then the relative position is associated to the caharacter + * before the meant position. */ - hasAttribute(attributeName: string): boolean; + assoc: number; + } + export function relativePositionToJSON(rpos: RelativePosition): any; + export function createRelativePositionFromJSON(json: any): RelativePosition; + export class AbsolutePosition { + constructor( + type: Y.Type.AbstractType, + index: number, + assoc?: number | undefined, + ); + type: Y.Type.AbstractType; + index: number; + assoc: number; + } + export function createAbsolutePosition( + type: Y.Type.AbstractType, + index: number, + assoc?: number | undefined, + ): AbsolutePosition; + export function createRelativePosition( + type: Y.Type.AbstractType, + item: Y.Struct.ID | null, + assoc?: number | undefined, + ): RelativePosition; + export function createRelativePositionFromTypeIndex( + type: Y.Type.AbstractType, + index: number, + assoc?: number | undefined, + ): RelativePosition; + export function encodeRelativePosition(rpos: RelativePosition): Uint8Array; + export function decodeRelativePosition( + uint8Array: Uint8Array, + ): RelativePosition; + export function createAbsolutePositionFromRelativePosition( + rpos: RelativePosition, + doc: Y.Doc, + followUndoneDeletions?: boolean, + ): AbsolutePosition | null; + export function compareRelativePositions( + a: RelativePosition | null, + b: RelativePosition | null, + ): boolean; +} +declare module Snapshot { + export class Snapshot { + constructor(ds: Y.Struct.DeleteSet, sv: Map); + ds: Y.Struct.DeleteSet; /** - * Returns all attribute name/value pairs in a JSON Object. - * - * @param {Snapshot} [snapshot] - * @return {{ [Key in Extract]?: KV[Key]}} A JSON Object that describes the attributes. + * State Map */ - getAttributes(snapshot?: Snapshot.Snapshot | undefined): { - [Key in Extract]?: KV[Key] | undefined; - }; + sv: Map; } + export function equalSnapshots(snap1: Snapshot, snap2: Snapshot): boolean; + export function encodeSnapshotV2( + snapshot: Snapshot, + encoder?: Y.Codec.DSEncoderV1 | Y.Codec.DSEncoderV2 | undefined, + ): Uint8Array; + export function encodeSnapshot(snapshot: Snapshot): Uint8Array; + export function decodeSnapshotV2( + buf: Uint8Array, + decoder?: Y.Codec.DSDecoderV1 | Y.Codec.DSDecoderV2 | undefined, + ): Snapshot; + export function decodeSnapshot(buf: Uint8Array): Snapshot; + export function createSnapshot( + ds: Y.Struct.DeleteSet, + sm: Map, + ): Snapshot; + export const emptySnapshot: Snapshot; + export function snapshot(doc: Y.Doc): Snapshot; + export function createDocFromSnapshot( + originDoc: Y.Doc, + snapshot: Snapshot, + newDoc?: Y.Doc | undefined, + ): Y.Doc; + export function snapshotContainsUpdate( + snapshot: Snapshot, + update: Uint8Array, + ): boolean; - type ValueTypes = - | Object + export function typeListToArraySnapshot( + type: Y.Type.AbstractType, + snapshot: Snapshot, + ): Array; + export function typeMapGetSnapshot( + parent: Y.Type.AbstractType, + key: string, + snapshot: Snapshot, + ): + | { + [x: string]: any; + } | number | null | Array | string | Uint8Array - | AbstractType; - - /** - * An Event that describes changes on a YXml Element or Yxml Fragment - */ - export class YXmlEvent extends YEvent< - | YXmlElement<{ - [key: string]: string; - }> - | YXmlFragment - | YXmlText - > { - /** - * @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: YXmlElement | YXmlText | YXmlFragment, - subs: Set, - transaction: Transaction, - ); - /** - * Set of all changed attributes. - * @type {Set} - */ - attributesChanged: Set; - } - - /** - * You can manage binding to a custom type with YXmlHook. - */ - export class YXmlHook extends YMap { - /** - * @param {string} hookName nodeName of the Dom Node. - */ - constructor(hookName: string); - hookName: string; - /** - * 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. - */ - clone(): YXmlHook; - /** - * 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} - */ - toDOM( - _document?: Document | undefined, - hooks?: - | { - [x: string]: any; - } - | undefined, - binding?: any, - ): Element; - } - - /** - * Represents text in a Dom Element. In the future this type will also handle - * simple formatting information like bold and italic. - */ - export class YXmlText extends YText { - get nextSibling(): - | YXmlElement<{ - [key: string]: string; - }> - | YXmlText - | null; - get prevSibling(): - | YXmlElement<{ - [key: string]: string; - }> - | YXmlText - | null; - /** - * 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. - */ - clone(): YXmlText; - /** - * Creates a Dom Element that mirrors this YXmlText. - * - * @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} - */ - toDOM( - _document?: Document | undefined, - hooks?: - | { - [x: string]: any; - } - | undefined, - binding?: any, - ): Text; - toString(): any; - } + | Y.Type.AbstractType + | undefined; + export function typeMapGetAllSnapshot( + parent: Y.Type.AbstractType, + snapshot: Snapshot, + ): { + [x: string]: + | { + [x: string]: any; + } + | number + | null + | Array + | string + | Uint8Array + | Y.Type.AbstractType + | undefined; + }; } -declare namespace InternalStruct { - export class AbstractStruct { - constructor(id: ID, length: number); - id: ID; - length: number; - get deleted(): boolean; - /** - * Merge this struct with the item to the right. - * 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 - */ - mergeWith(right: AbstractStruct): boolean; - /** - * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder The encoder to write data to. - * @param {number} offset - * @param {number} encodingRef - */ - write( - encoder: UpdateEncoderV1 | UpdateEncoderV2, - offset: number, - encodingRef: number, +declare module Utils { + export class PermanentUserData { + constructor(doc: Y.Doc, storeType?: Y.Type.YMap | undefined); + yusers: Y.Type.YMap; + doc: Y.Doc; + /** + * Maps from clientid to userDescription + */ + clients: Map; + dss: Map; + setUserMapping( + doc: Y.Doc, + clientid: number, + userDescription: string, + conf?: { + filter?: + | ((arg0: Y.Transaction, arg1: Y.Struct.DeleteSet) => boolean) + | undefined; + }, ): void; - integrate(transaction: Transaction, offset: number): void; + getUserByClientId(clientid: number): any; + getUserByDeletedId(id: Y.Struct.ID): string | null; } - export class GC extends AbstractStruct { - delete(): void; - mergeWith(right: GC): boolean; - write(encoder: UpdateEncoderV1 | UpdateEncoderV2, offset: number): void; - getMissing(transaction: Transaction, store: StructStore): null | number; + export class StackItem { + constructor(deletions: Y.Struct.DeleteSet, insertions: Y.Struct.DeleteSet); + insertions: Y.Struct.DeleteSet; + deletions: Y.Struct.DeleteSet; + /** + * Use this to save and restore metadata like selection range + */ + meta: Map; } - /** - * Abstract class that represents any content. + * 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 + * metadata property on `event.stackItem.meta` (it is a `Map` of metadata properties). + * 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`. */ - export class Item extends AbstractStruct { + export class UndoManager extends ObservableV2<{ + "stack-item-added": (arg0: StackItemEvent, arg1: UndoManager) => void; + "stack-item-popped": (arg0: StackItemEvent, arg1: UndoManager) => void; + "stack-cleared": (arg0: { + undoStackCleared: boolean; + redoStackCleared: boolean; + }) => void; + "stack-item-updated": (arg0: StackItemEvent, arg1: UndoManager) => void; + }> { /** - * @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|Array>} typeScope Accepts either a single type, or an array of types + * @param {UndoManagerOptions} options */ constructor( - id: ID, - left: Item | null, - origin: ID | null, - right: Item | null, - rightOrigin: ID | null, - parent: AbstractType | ID | null, - parentSub: string | null, - content: AbstractContent, + typeScope: Y.Type.AbstractType | Array>, + options?: UndoManagerOptions, ); /** - * The item that was originally to the left of this item. - */ - origin: ID | null; - /** - * The item that is currently to the left of this item. - */ - left: Item | null; - /** - * The item that is currently to the right of this item. - */ - right: Item | null; - /** - * The item that was originally to the right of this item. - */ - rightOrigin: ID | null; - /** - * @type {AbstractType|ID|null} - */ - parent: AbstractType | ID | null; - /** - * If the parent refers to this item with some kind of key (e.g. YMap, the - * key is specified here. The key is then used to refer to the list in which - * to insert this item. If `parentSub = null` type._start is the list in - * which to insert to. Otherwise it is `parent._map`. - */ - parentSub: string | null; - /** - * If this type's effect is redone this type refers to the type that undid - * this operation. - */ - redone: ID | null; - content: AbstractContent; - /** - * bit1: keep - * bit2: countable - * bit3: deleted - * bit4: mark - mark node as fast-search-marker - * @type {number} byte + * @type {Array>} */ - info: number; + scope: Array>; + doc: Y.Doc; + deleteFilter: (arg0: Y.Struct.Item) => boolean; + trackedOrigins: Set; + captureTransaction: (arg0: Y.Transaction) => boolean; + undoStack: Array; + redoStack: Array; /** - * This is used to mark the item as an indexed fast-search marker + * Whether the client is currently undoing (calling UndoManager.undo) */ - set marker(arg: boolean); - get marker(): boolean; - set keep(arg: boolean); + undoing: boolean; + redoing: boolean; /** - * If true, do not garbage collect this Item. + * The currently popped stack item if UndoManager.undoing or UndoManager.redoing */ - get keep(): boolean; - get countable(): boolean; - set deleted(arg: boolean); + currStackItem: StackItem | null; + lastChange: number; + ignoreRemoteMapChanges: boolean; + captureTimeout: number; + afterTransactionHandler: (transaction: Y.Transaction) => void; + addToScope( + ytypes: Array> | Y.Type.AbstractType, + ): void; + addTrackedOrigin(origin: any): void; + removeTrackedOrigin(origin: any): void; + clear(clearUndoStack?: boolean, clearRedoStack?: boolean): void; /** - * Whether this item was deleted or not. + * UndoManager merges Undo-StackItem if they are created within time-gap + * smaller than `options.captureTimeout`. Call `um.stopCapturing()` so that the next + * StackItem won't be merged. + * + * + * @example + * // without stopCapturing + * ytext.insert(0, 'a') + * ytext.insert(1, 'b') + * um.undo() + * ytext.toString() // => '' (note that 'ab' was removed) + * // with stopCapturing + * ytext.insert(0, 'a') + * um.stopCapturing() + * ytext.insert(0, 'b') + * um.undo() + * ytext.toString() // => 'a' (note that only 'b' was removed) + * */ - get deleted(): boolean; - markDeleted(): void; + stopCapturing(): void; /** - * Return the creator clientID of the missing op or define missing items and return null. + * Undo last changes on type. */ - getMissing(transaction: Transaction, store: StructStore): null | number; + undo(): StackItem | null; /** - * Returns the next non-deleted item + * Redo last undo operation. */ - get next(): Item | null; + redo(): StackItem | null; /** - * Returns the previous non-deleted item + * Are undo steps available? */ - get prev(): Item | null; + canUndo(): boolean; /** - * Computes the last content address of this Item. + * Are redo steps available? */ - get lastId(): ID; + canRedo(): boolean; + } + type UndoManagerOptions = { + captureTimeout?: number | undefined; /** - * Try to merge two items + * Do not capture changes of a Transaction if result false. */ - mergeWith(right: Item): boolean; + captureTransaction?: ((arg0: Y.Transaction) => boolean) | undefined; /** - * Mark this Item as deleted. + * Sometimes + * it is necessary to filter what an Undo/Redo operation can delete. If this + * filter returns false, the type/item won't be deleted even it is in the + * undo/redo scope. */ - delete(transaction: Transaction): void; - gc(store: StructStore, parentGCd: boolean): void; + deleteFilter?: ((arg0: Y.Struct.Item) => boolean) | undefined; + trackedOrigins?: Set | undefined; /** - * Transform the properties of this type to binary and write it to an - * BinaryEncoder. - * - * This is called when this Item is sent to a remote peer. - * - * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder The encoder to write data to. - * @param {number} offset + * Experimental. By default, the UndoManager will never overwrite remote changes. Enable this property to enable overwriting remote changes on key-value changes (Y.Map, properties on Y.Xml, etc..). */ - write(encoder: UpdateEncoderV1 | UpdateEncoderV2, offset: number): void; - } - - export class AbstractContent { - getLength(): number; - getContent(): Array; + ignoreRemoteMapChanges?: boolean | undefined; /** - * Should return false if this Item is some kind of meta information - * (e.g. format information). - * - * * Whether this Item should be addressable via `yarray.get(i)` - * * Whether this Item should be counted when computing yarray.length + * The document that this UndoManager operates on. Only needed if typeScope is empty. */ - isCountable(): boolean; - copy(): AbstractContent; - splice(_offset: number): AbstractContent; - mergeWith(_right: AbstractContent): boolean; - integrate(_transaction: Transaction, _item: Item): void; - delete(_transaction: Transaction): void; - gc(_store: StructStore): void; - write(_encoder: UpdateEncoderV1 | UpdateEncoderV2, _offset: number): void; - getRef(): number; - } - - export class Skip extends AbstractStruct { - delete(): void; - mergeWith(right: Skip): boolean; - write(encoder: UpdateEncoderV1 | UpdateEncoderV2, offset: number): void; - getMissing(transaction: Transaction, store: StructStore): null | number; - } + doc?: Y.Doc | undefined; + }; + type StackItemEvent = { + stackItem: StackItem; + origin: any; + type: "undo" | "redo"; + changedParentTypes: Map< + Y.Type.AbstractType>, + Array> + >; + }; } From d8e78920f18f855f2846b48359009a18f8f1522f Mon Sep 17 00:00:00 2001 From: DarkSky Date: Mon, 1 Jul 2024 16:58:19 +0800 Subject: [PATCH 04/75] chore: rename export name similar to yjs --- y-octo-node/index.d.ts | 15 ++++++++++----- y-octo-node/index.js | 8 ++++---- y-octo-node/src/array.rs | 11 +++++++++-- y-octo-node/src/doc.rs | 24 ++++++++++++------------ y-octo-node/src/lib.rs | 2 +- y-octo-node/src/map.rs | 4 ++-- y-octo-node/src/text.rs | 6 +++--- 7 files changed, 41 insertions(+), 29 deletions(-) diff --git a/y-octo-node/index.d.ts b/y-octo-node/index.d.ts index 1aaf166..f11550f 100644 --- a/y-octo-node/index.d.ts +++ b/y-octo-node/index.d.ts @@ -3,7 +3,9 @@ /* auto-generated by NAPI-RS */ -export class YArray { +export type YArray = Array +export class Array { + constructor() get length(): number get isEmpty(): boolean get(index: number): T @@ -15,15 +17,16 @@ export class YArray { delete(index: number, len: number): void toJson(): JsArray } +export type YDoc = Doc export class Doc { constructor(clientId?: number | undefined | null) get clientId(): number get guid(): string get keys(): Array - getOrCreateArray(key: string): YArray + getOrCreateArray(key: string): Array getOrCreateText(key: string): YText getOrCreateMap(key: string): YMap - createArray(): YArray + createArray(): Array createText(): YText createMap(): YMap applyUpdate(update: Buffer): void @@ -31,7 +34,8 @@ export class Doc { gc(): void onUpdate(callback: (result: Uint8Array) => void): void } -export class YMap { +export type YMap = Map +export class Map { constructor() get length(): number get isEmpty(): boolean @@ -40,7 +44,8 @@ export class YMap { remove(key: string): void toJson(): object } -export class YText { +export type YText = Text +export class Text { constructor() get len(): number get isEmpty(): boolean diff --git a/y-octo-node/index.js b/y-octo-node/index.js index 516ad7e..1124a6b 100644 --- a/y-octo-node/index.js +++ b/y-octo-node/index.js @@ -252,9 +252,9 @@ if (!nativeBinding) { throw new Error(`Failed to load native binding`) } -const { YArray, Doc, YMap, YText } = nativeBinding +const { Array, Doc, Map, Text } = nativeBinding -module.exports.YArray = YArray +module.exports.Array = Array module.exports.Doc = Doc -module.exports.YMap = YMap -module.exports.YText = YText +module.exports.Map = Map +module.exports.Text = Text diff --git a/y-octo-node/src/array.rs b/y-octo-node/src/array.rs index 37326e7..8d04a13 100644 --- a/y-octo-node/src/array.rs +++ b/y-octo-node/src/array.rs @@ -3,13 +3,20 @@ use y_octo::{Any, Array, Value}; use super::*; -#[napi] +#[napi(js_name = "Array")] +#[derive(Clone)] pub struct YArray { pub(crate) array: Array, } #[napi] impl YArray { + // patch for Array define in node port + #[napi(constructor)] + pub fn new() -> Self { + unimplemented!() + } + pub(crate) fn inner_new(array: Array) -> Self { Self { array } } @@ -163,7 +170,7 @@ mod tests { #[test] fn test_array_init() { - let doc = Doc::new(None); + let doc = YDoc::new(None); let array = doc.get_or_create_array("array".into()).unwrap(); assert_eq!(array.length(), 0); } diff --git a/y-octo-node/src/doc.rs b/y-octo-node/src/doc.rs index e37ab80..2822712 100644 --- a/y-octo-node/src/doc.rs +++ b/y-octo-node/src/doc.rs @@ -2,24 +2,24 @@ use napi::{ bindgen_prelude::{Buffer as JsBuffer, JsFunction}, threadsafe_function::{ErrorStrategy, ThreadsafeFunction, ThreadsafeFunctionCallMode}, }; -use y_octo::{CrdtRead, Doc as YDoc, History, RawDecoder, StateVector}; +use y_octo::{CrdtRead, Doc, History, RawDecoder, StateVector}; use super::*; -#[napi] -pub struct Doc { - doc: YDoc, +#[napi(js_name = "Doc")] +pub struct YDoc { + doc: Doc, } #[napi] -impl Doc { +impl YDoc { #[napi(constructor)] pub fn new(client_id: Option) -> Self { Self { doc: if let Some(client_id) = client_id { - YDoc::with_client(client_id as u64) + Doc::with_client(client_id as u64) } else { - YDoc::default() + Doc::default() }, } } @@ -130,33 +130,33 @@ mod tests { #[test] fn test_doc_client() { let client_id = 1; - let doc = Doc::new(Some(client_id)); + let doc = YDoc::new(Some(client_id)); assert_eq!(doc.client_id(), 1); } #[test] fn test_doc_guid() { - let doc = Doc::new(None); + let doc = YDoc::new(None); assert_eq!(doc.guid().len(), 21); } #[test] fn test_create_array() { - let doc = Doc::new(None); + let doc = YDoc::new(None); let array = doc.get_or_create_array("array".into()).unwrap(); assert_eq!(array.length(), 0); } #[test] fn test_create_text() { - let doc = Doc::new(None); + let doc = YDoc::new(None); let text = doc.get_or_create_text("text".into()).unwrap(); assert_eq!(text.len(), 0); } #[test] fn test_keys() { - let doc = Doc::new(None); + let doc = YDoc::new(None); doc.get_or_create_array("array".into()).unwrap(); doc.get_or_create_text("text".into()).unwrap(); doc.get_or_create_map("map".into()).unwrap(); diff --git a/y-octo-node/src/lib.rs b/y-octo-node/src/lib.rs index f014828..eb32f66 100644 --- a/y-octo-node/src/lib.rs +++ b/y-octo-node/src/lib.rs @@ -8,7 +8,7 @@ mod text; mod utils; pub use array::YArray; -pub use doc::Doc; +pub use doc::YDoc; pub use map::YMap; pub use text::YText; use utils::{ diff --git a/y-octo-node/src/map.rs b/y-octo-node/src/map.rs index 4d1fe08..26c073f 100644 --- a/y-octo-node/src/map.rs +++ b/y-octo-node/src/map.rs @@ -3,7 +3,7 @@ use y_octo::{Any, Map, Value}; use super::*; -#[napi] +#[napi(js_name = "Map")] pub struct YMap { pub(crate) map: Map, } @@ -114,7 +114,7 @@ mod tests { #[test] fn test_map_init() { - let doc = Doc::new(None); + let doc = YDoc::new(None); let text = doc.get_or_create_map("map".into()).unwrap(); assert_eq!(text.length(), 0); } diff --git a/y-octo-node/src/text.rs b/y-octo-node/src/text.rs index cd4fadb..2271e00 100644 --- a/y-octo-node/src/text.rs +++ b/y-octo-node/src/text.rs @@ -2,7 +2,7 @@ use y_octo::Text; use super::*; -#[napi] +#[napi(js_name = "Text")] pub struct YText { pub(crate) text: Text, } @@ -57,14 +57,14 @@ mod tests { #[test] fn test_text_init() { - let doc = Doc::new(None); + let doc = YDoc::new(None); let text = doc.get_or_create_text("text".into()).unwrap(); assert_eq!(text.len(), 0); } #[test] fn test_text_edit() { - let doc = Doc::new(None); + let doc = YDoc::new(None); let mut text = doc.get_or_create_text("text".into()).unwrap(); text.insert(0, "hello".into()).unwrap(); assert_eq!(text.to_string(), "hello"); From 043f1444c8892e57f3c1606cd842a769ade25495 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Tue, 2 Jul 2024 12:19:45 +0800 Subject: [PATCH 05/75] feat: add utils for updates --- y-octo-node/index.d.ts | 3 +++ y-octo-node/index.js | 5 ++++- y-octo-node/src/function.rs | 20 ++++++++++++++++++++ y-octo-node/src/lib.rs | 3 +++ y-octo-node/tests/array.spec.mts | 4 ++-- y-octo-node/tests/doc.spec.mts | 8 ++++---- y-octo/src/doc/document.rs | 4 ++++ 7 files changed, 40 insertions(+), 7 deletions(-) create mode 100644 y-octo-node/src/function.rs diff --git a/y-octo-node/index.d.ts b/y-octo-node/index.d.ts index f11550f..f6ad3b0 100644 --- a/y-octo-node/index.d.ts +++ b/y-octo-node/index.d.ts @@ -3,6 +3,9 @@ /* auto-generated by NAPI-RS */ +export declare function encodeStateAsUpdate(doc: Doc, state?: Buffer | undefined | null): Buffer +export declare function applyUpdate(doc: Doc, update: Buffer): void +export declare function mergeUpdates(updates: Array): Buffer export type YArray = Array export class Array { constructor() diff --git a/y-octo-node/index.js b/y-octo-node/index.js index 1124a6b..745f155 100644 --- a/y-octo-node/index.js +++ b/y-octo-node/index.js @@ -252,9 +252,12 @@ if (!nativeBinding) { throw new Error(`Failed to load native binding`) } -const { Array, Doc, Map, Text } = nativeBinding +const { Array, Doc, encodeStateAsUpdate, applyUpdate, mergeUpdates, Map, Text } = nativeBinding module.exports.Array = Array module.exports.Doc = Doc +module.exports.encodeStateAsUpdate = encodeStateAsUpdate +module.exports.applyUpdate = applyUpdate +module.exports.mergeUpdates = mergeUpdates module.exports.Map = Map module.exports.Text = Text diff --git a/y-octo-node/src/function.rs b/y-octo-node/src/function.rs new file mode 100644 index 0000000..537f340 --- /dev/null +++ b/y-octo-node/src/function.rs @@ -0,0 +1,20 @@ +use napi::bindgen_prelude::Buffer as JsBuffer; +use y_octo::merge_updates_v1; + +use super::*; + +#[napi] +pub fn encode_state_as_update(doc: &YDoc, state: Option) -> Result { + doc.encode_state_as_update_v1(state) +} + +#[napi] +pub fn apply_update(doc: &mut YDoc, update: JsBuffer) -> Result<()> { + doc.apply_update(update) +} + +#[napi] +pub fn merge_updates(updates: Vec) -> Result { + let updates = updates.iter().map(|u| u.as_ref()).collect::>(); + Ok(merge_updates_v1(updates)?.encode_v1()?.into()) +} diff --git a/y-octo-node/src/lib.rs b/y-octo-node/src/lib.rs index eb32f66..a154867 100644 --- a/y-octo-node/src/lib.rs +++ b/y-octo-node/src/lib.rs @@ -3,14 +3,17 @@ use napi_derive::napi; mod array; mod doc; +mod function; mod map; mod text; mod utils; pub use array::YArray; pub use doc::YDoc; +pub use function::{apply_update, encode_state_as_update, merge_updates}; pub use map::YMap; pub use text::YText; + use utils::{ get_any_from_js_object, get_any_from_js_unknown, get_js_unknown_from_any, get_js_unknown_from_value, MixedRefYType, MixedYType, diff --git a/y-octo-node/tests/array.spec.mts b/y-octo-node/tests/array.spec.mts index ae2aa47..35eb276 100644 --- a/y-octo-node/tests/array.spec.mts +++ b/y-octo-node/tests/array.spec.mts @@ -1,7 +1,7 @@ import assert, { equal, deepEqual } from "node:assert"; import { test } from "node:test"; -import { Doc, YArray } from "../index"; +import { Doc, Array } from "../index"; test("array test", { concurrency: false }, async (t) => { let client_id: number; @@ -56,7 +56,7 @@ test("array test", { concurrency: false }, async (t) => { sub.insert(3, "hello world"); equal(sub.length, 4); - let sub2 = map.get("sub"); + let sub2 = map.get("sub"); assert(sub2); equal(sub2.get(0), true); equal(sub2.get(1), false); diff --git a/y-octo-node/tests/doc.spec.mts b/y-octo-node/tests/doc.spec.mts index d854746..1cde13d 100644 --- a/y-octo-node/tests/doc.spec.mts +++ b/y-octo-node/tests/doc.spec.mts @@ -1,15 +1,15 @@ import assert, { equal } from "node:assert"; import { test } from "node:test"; -import { Doc } from "../index"; +import * as YOcto from "../index"; import * as Y from "yjs"; test("doc test", { concurrency: false }, async (t) => { let client_id: number; - let doc: Doc; + let doc: YOcto.Doc; t.beforeEach(async () => { client_id = (Math.random() * 100000) | 0; - doc = new Doc(client_id); + doc = new YOcto.Doc(client_id); }); t.afterEach(async () => { @@ -39,7 +39,7 @@ test("doc test", { concurrency: false }, async (t) => { text.insert(1, "b"); text.insert(2, "c"); - let doc2 = new Doc(client_id); + let doc2 = new YOcto.Doc(client_id); doc2.applyUpdate(doc.encodeStateAsUpdateV1()); let array2 = doc2.getOrCreateArray("array"); diff --git a/y-octo/src/doc/document.rs b/y-octo/src/doc/document.rs index 8acad3f..a7faf17 100644 --- a/y-octo/src/doc/document.rs +++ b/y-octo/src/doc/document.rs @@ -176,6 +176,10 @@ impl Doc { self.client_id } + pub fn set_client(&mut self, client_id: u64) { + self.client_id = client_id; + } + pub fn clients(&self) -> Vec { self.store.read().unwrap().clients() } From c2e3f52b9493c6cd45ed7e9d6eec197198e3b8e6 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Tue, 2 Jul 2024 14:55:48 +0800 Subject: [PATCH 06/75] feat: export more funcs --- y-octo-node/src/doc.rs | 7 ++++++- y-octo-node/src/lib.rs | 12 +++++++----- y-octo-node/src/types.rs | 36 ++++++++++++++++++++++++++++++++++++ y-octo/src/doc/document.rs | 4 ++++ y-octo/src/doc/store.rs | 4 ++++ y-octo/src/lib.rs | 6 +++--- 6 files changed, 60 insertions(+), 9 deletions(-) create mode 100644 y-octo-node/src/types.rs diff --git a/y-octo-node/src/doc.rs b/y-octo-node/src/doc.rs index 2822712..1a7a834 100644 --- a/y-octo-node/src/doc.rs +++ b/y-octo-node/src/doc.rs @@ -8,7 +8,7 @@ use super::*; #[napi(js_name = "Doc")] pub struct YDoc { - doc: Doc, + pub(crate) doc: Doc, } #[napi] @@ -34,6 +34,11 @@ impl YDoc { self.doc.guid() } + #[napi(getter)] + pub fn store(&self) -> YStore { + YStore { doc: self.doc.clone() } + } + #[napi(getter)] pub fn keys(&self) -> Vec { self.doc.keys() diff --git a/y-octo-node/src/lib.rs b/y-octo-node/src/lib.rs index a154867..0310c78 100644 --- a/y-octo-node/src/lib.rs +++ b/y-octo-node/src/lib.rs @@ -6,13 +6,15 @@ mod doc; mod function; mod map; mod text; +mod types; mod utils; -pub use array::YArray; -pub use doc::YDoc; -pub use function::{apply_update, encode_state_as_update, merge_updates}; -pub use map::YMap; -pub use text::YText; +pub use array::*; +pub use doc::*; +pub use function::*; +pub use map::*; +pub use text::*; +pub use types::*; use utils::{ get_any_from_js_object, get_any_from_js_unknown, get_js_unknown_from_any, get_js_unknown_from_value, MixedRefYType, diff --git a/y-octo-node/src/types.rs b/y-octo-node/src/types.rs new file mode 100644 index 0000000..6d112a9 --- /dev/null +++ b/y-octo-node/src/types.rs @@ -0,0 +1,36 @@ +use y_octo::{CrdtWrite, DeleteSet, Doc, RawEncoder, StateVector}; + +use super::*; + +#[napi(js_name = "Store")] +pub struct YStore { + pub(crate) doc: Doc, +} + +#[napi(js_name = "DeleteSet")] +#[derive(PartialEq)] +pub struct YDeleteSet { + pub(crate) ds: DeleteSet, +} + +#[napi] +pub struct YSnapshot { + ds: DeleteSet, + sv: StateVector, +} + +impl YSnapshot { + pub fn from_doc(doc: &YDoc) -> Self { + Self { + ds: doc.doc.get_delete_sets(), + sv: doc.doc.get_state_vector(), + } + } + + pub fn encode_v1(&self) -> Result> { + let mut encoder = RawEncoder::default(); + self.ds.write(&mut encoder)?; + self.sv.write(&mut encoder)?; + Ok(encoder.into_inner()) + } +} diff --git a/y-octo/src/doc/document.rs b/y-octo/src/doc/document.rs index a7faf17..274f5ac 100644 --- a/y-octo/src/doc/document.rs +++ b/y-octo/src/doc/document.rs @@ -365,6 +365,10 @@ impl Doc { self.store.read().unwrap().get_state_vector() } + pub fn get_delete_sets(&self) -> DeleteSet { + self.store.read().unwrap().get_delete_sets() + } + pub fn subscribe(&self, cb: impl Fn(&[u8], &[History]) + Sync + Send + 'static) { self.publisher.subscribe(cb); } diff --git a/y-octo/src/doc/store.rs b/y-octo/src/doc/store.rs index d13eaea..9e2088a 100644 --- a/y-octo/src/doc/store.rs +++ b/y-octo/src/doc/store.rs @@ -101,6 +101,10 @@ impl DocStore { Self::items_as_state_vector(&self.items) } + pub fn get_delete_sets(&self) -> DeleteSet { + self.delete_set.clone() + } + fn items_as_state_vector(items: &ClientMap>) -> StateVector { let mut state = StateVector::default(); for (client, structs) in items.iter() { diff --git a/y-octo/src/lib.rs b/y-octo/src/lib.rs index e7e8ade..ccced92 100644 --- a/y-octo/src/lib.rs +++ b/y-octo/src/lib.rs @@ -7,9 +7,9 @@ mod sync; pub use codec::*; pub use doc::{ encode_awareness_as_message, encode_update_as_message, merge_updates_v1, Any, Array, Awareness, AwarenessEvent, - Client, ClientMap, Clock, CrdtRead, CrdtReader, CrdtWrite, CrdtWriter, Doc, DocOptions, HashMap as AHashMap, - HashMapExt, History, HistoryOptions, Id, Map, RawDecoder, RawEncoder, StateVector, StoreHistory, Text, Update, - Value, + Client, ClientMap, Clock, CrdtRead, CrdtReader, CrdtWrite, CrdtWriter, DeleteSet, Doc, DocOptions, + HashMap as AHashMap, HashMapExt, History, HistoryOptions, Id, Map, RawDecoder, RawEncoder, StateVector, + StoreHistory, Text, Update, Value, }; pub(crate) use doc::{Content, Item}; use log::{debug, warn}; From 53853f4afb84fa4d0272ac913bb3d540744ed2fa Mon Sep 17 00:00:00 2001 From: DarkSky Date: Tue, 2 Jul 2024 15:51:17 +0800 Subject: [PATCH 07/75] feat: export new utils functions --- y-octo-node/index.d.ts | 12 ++++++++++ y-octo-node/index.js | 11 ++++++++- y-octo-node/src/function.rs | 47 +++++++++++++++++++++++++++++++++++-- 3 files changed, 67 insertions(+), 3 deletions(-) diff --git a/y-octo-node/index.d.ts b/y-octo-node/index.d.ts index f6ad3b0..9ff6f86 100644 --- a/y-octo-node/index.d.ts +++ b/y-octo-node/index.d.ts @@ -4,8 +4,14 @@ /* auto-generated by NAPI-RS */ export declare function encodeStateAsUpdate(doc: Doc, state?: Buffer | undefined | null): Buffer +export declare function encodeStateVector(doc: Doc): Buffer +export declare function createDeleteSetFromStructStore(store: YStore): YDeleteSet +export declare function equalDeleteSets(a: YDeleteSet, b: YDeleteSet): boolean +export declare function snapshot(doc: Doc): YSnapshot +export declare function encodeSnapshot(snapshot: YSnapshot): Buffer export declare function applyUpdate(doc: Doc, update: Buffer): void export declare function mergeUpdates(updates: Array): Buffer +export declare function isAbstractType(unknown: unknown): boolean export type YArray = Array export class Array { constructor() @@ -25,6 +31,7 @@ export class Doc { constructor(clientId?: number | undefined | null) get clientId(): number get guid(): string + get store(): YStore get keys(): Array getOrCreateArray(key: string): Array getOrCreateText(key: string): YText @@ -57,3 +64,8 @@ export class Text { get length(): number toString(): string } +export type YStore = Store +export class Store { } +export type YDeleteSet = DeleteSet +export class DeleteSet { } +export class YSnapshot { } diff --git a/y-octo-node/index.js b/y-octo-node/index.js index 745f155..190c7da 100644 --- a/y-octo-node/index.js +++ b/y-octo-node/index.js @@ -252,12 +252,21 @@ if (!nativeBinding) { throw new Error(`Failed to load native binding`) } -const { Array, Doc, encodeStateAsUpdate, applyUpdate, mergeUpdates, Map, Text } = nativeBinding +const { Array, Doc, encodeStateAsUpdate, encodeStateVector, createDeleteSetFromStructStore, equalDeleteSets, snapshot, encodeSnapshot, applyUpdate, mergeUpdates, isAbstractType, Map, Text, Store, DeleteSet, YSnapshot } = nativeBinding module.exports.Array = Array module.exports.Doc = Doc module.exports.encodeStateAsUpdate = encodeStateAsUpdate +module.exports.encodeStateVector = encodeStateVector +module.exports.createDeleteSetFromStructStore = createDeleteSetFromStructStore +module.exports.equalDeleteSets = equalDeleteSets +module.exports.snapshot = snapshot +module.exports.encodeSnapshot = encodeSnapshot module.exports.applyUpdate = applyUpdate module.exports.mergeUpdates = mergeUpdates +module.exports.isAbstractType = isAbstractType module.exports.Map = Map module.exports.Text = Text +module.exports.Store = Store +module.exports.DeleteSet = DeleteSet +module.exports.YSnapshot = YSnapshot diff --git a/y-octo-node/src/function.rs b/y-octo-node/src/function.rs index 537f340..b1d2c6c 100644 --- a/y-octo-node/src/function.rs +++ b/y-octo-node/src/function.rs @@ -1,13 +1,51 @@ -use napi::bindgen_prelude::Buffer as JsBuffer; -use y_octo::merge_updates_v1; +use napi::{bindgen_prelude::Buffer as JsBuffer, Env, JsUnknown}; +use y_octo::{merge_updates_v1, CrdtWrite, RawEncoder}; use super::*; +// state + #[napi] pub fn encode_state_as_update(doc: &YDoc, state: Option) -> Result { doc.encode_state_as_update_v1(state) } +#[napi] +pub fn encode_state_vector(doc: &YDoc) -> Result { + let sv = doc.doc.get_state_vector(); + let mut encoder = RawEncoder::default(); + sv.write(&mut encoder)?; + Ok(encoder.into_inner().into()) +} + +// delete set + +#[napi] +pub fn create_delete_set_from_struct_store(store: &YStore) -> YDeleteSet { + YDeleteSet { + ds: store.doc.get_delete_sets(), + } +} + +#[napi] +pub fn equal_delete_sets(a: &YDeleteSet, b: &YDeleteSet) -> bool { + a == b +} + +// snapshot + +#[napi] +pub fn snapshot(doc: &YDoc) -> YSnapshot { + YSnapshot::from_doc(doc) +} + +#[napi] +pub fn encode_snapshot(snapshot: &YSnapshot) -> Result { + Ok(snapshot.encode_v1()?.into()) +} + +// update + #[napi] pub fn apply_update(doc: &mut YDoc, update: JsBuffer) -> Result<()> { doc.apply_update(update) @@ -18,3 +56,8 @@ pub fn merge_updates(updates: Vec) -> Result { let updates = updates.iter().map(|u| u.as_ref()).collect::>(); Ok(merge_updates_v1(updates)?.encode_v1()?.into()) } + +#[napi] +pub fn is_abstract_type(env: Env, unknown: JsUnknown) -> Result { + Ok(YArray::instance_of(env, &unknown)? || YMap::instance_of(env, &unknown)? || YText::instance_of(env, &unknown)?) +} From 9f2873d04b9463a36f9796981a786c2fdfd2c503 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Tue, 2 Jul 2024 17:42:40 +0800 Subject: [PATCH 08/75] feat: impl iterator for array --- y-octo-node/index.d.ts | 4 ++++ y-octo-node/src/array.rs | 39 +++++++++++++++++++++++++++++++- y-octo-node/tests/array.spec.mts | 24 ++++++++++++++++++++ 3 files changed, 66 insertions(+), 1 deletion(-) diff --git a/y-octo-node/index.d.ts b/y-octo-node/index.d.ts index 9ff6f86..e27a629 100644 --- a/y-octo-node/index.d.ts +++ b/y-octo-node/index.d.ts @@ -25,6 +25,10 @@ export class Array { unshift(value: YArray | YMap | YText | boolean | number | string | Record | null | undefined): void delete(index: number, len: number): void toJson(): JsArray + iter(): YArrayIterator +} +export class YArrayIterator { + [Symbol.iterator](): Iterator } export type YDoc = Doc export class Doc { diff --git a/y-octo-node/src/array.rs b/y-octo-node/src/array.rs index 8d04a13..b8446c1 100644 --- a/y-octo-node/src/array.rs +++ b/y-octo-node/src/array.rs @@ -3,7 +3,7 @@ use y_octo::{Any, Array, Value}; use super::*; -#[napi(js_name = "Array")] +#[napi] #[derive(Clone)] pub struct YArray { pub(crate) array: Array, @@ -161,6 +161,43 @@ impl YArray { } Ok(js_array) } + #[napi] + pub fn iter(&self, env: Env) -> YArrayIterator { + YArrayIterator { + array: self.clone(), + env, + current: 0, + } + } +} + +#[napi(iterator)] +pub struct YArrayIterator { + array: YArray, + env: Env, + current: i64, +} + +#[napi] +impl Generator for YArrayIterator { + type Yield = MixedYType; + + type Next = Option; + + type Return = (); + + fn next(&mut self, value: Option) -> Option { + if self.array.length() <= self.current { + return None; + } + let ret = self.array.get(self.env, self.current).ok(); + self.current = if let Some(value) = value.and_then(|v| v) { + value + } else { + self.current + 1 + }; + ret + } } #[cfg(test)] diff --git a/y-octo-node/tests/array.spec.mts b/y-octo-node/tests/array.spec.mts index 35eb276..d236ce2 100644 --- a/y-octo-node/tests/array.spec.mts +++ b/y-octo-node/tests/array.spec.mts @@ -70,5 +70,29 @@ test("array test", { concurrency: false }, async (t) => { ); }); + await t.test("array should support iterator", () => { + let arr = doc.getOrCreateArray("arr"); + arr.insert(0, true); + arr.insert(1, false); + arr.insert(2, 1); + arr.insert(3, "hello world"); + let i = 0; + for (let v of arr.iter()) { + switch (i) { + case 0: + equal(v, true); + break; + case 1: + equal(v, false); + break; + case 2: + equal(v, 1); + break; + case 3: + equal(v, "hello world"); + break; + } + i++; + } }); }); From 57595622cf058c0edda5dab1ae4930e725e1dd4a Mon Sep 17 00:00:00 2001 From: DarkSky Date: Wed, 3 Jul 2024 14:51:36 +0800 Subject: [PATCH 09/75] feat: match more func signature --- y-octo-node/index.d.ts | 12 +- y-octo-node/src/array.rs | 45 +- y-octo-node/src/doc.rs | 8 + y-octo-node/src/map.rs | 14 +- y-octo-node/tests/map.spec.mts | 10 +- y-octo-node/tests/text.spec.mts | 4 +- y-octo-node/tests/yjs/testHelper.ts | 515 +++++++++++++++++++++++ y-octo-node/tests/yjs/yarray.spec.mts | 582 ++++++++++++++++++++++++++ 8 files changed, 1169 insertions(+), 21 deletions(-) create mode 100644 y-octo-node/tests/yjs/testHelper.ts create mode 100644 y-octo-node/tests/yjs/yarray.spec.mts diff --git a/y-octo-node/index.d.ts b/y-octo-node/index.d.ts index e27a629..fb00ea5 100644 --- a/y-octo-node/index.d.ts +++ b/y-octo-node/index.d.ts @@ -18,14 +18,17 @@ export class Array { get length(): number get isEmpty(): boolean get(index: number): T - slice(start: number, end: number): Array + slice(start: number, end?: number | undefined | null): Array map(callback: (...args: any[]) => any): Array insert(index: number, value: YArray | YMap | YText | boolean | number | string | Record | null | undefined): void push(value: YArray | YMap | YText | boolean | number | string | Record | null | undefined): void unshift(value: YArray | YMap | YText | boolean | number | string | Record | null | undefined): void - delete(index: number, len: number): void - toJson(): JsArray + delete(index: number, len?: number | undefined | null): void iter(): YArrayIterator + toArray(): JsArray + toJSON(): JsArray + observe(callback: (...args: any[]) => any): void + observeDeep(callback: (...args: any[]) => any): void } export class YArrayIterator { [Symbol.iterator](): Iterator @@ -47,6 +50,7 @@ export class Doc { encodeStateAsUpdateV1(state?: Buffer | undefined | null): Buffer gc(): void onUpdate(callback: (result: Uint8Array) => void): void + transact(callback: (...args: any[]) => any): void } export type YMap = Map export class Map { @@ -57,6 +61,8 @@ export class Map { set(key: string, value: YArray | YMap | YText | boolean | number | string | Record | null | undefined): void remove(key: string): void toJson(): object + observe(callback: (...args: any[]) => any): void + observeDeep(callback: (...args: any[]) => any): void } export type YText = Text export class Text { diff --git a/y-octo-node/src/array.rs b/y-octo-node/src/array.rs index b8446c1..80bbfbb 100644 --- a/y-octo-node/src/array.rs +++ b/y-octo-node/src/array.rs @@ -48,9 +48,10 @@ impl YArray { } #[napi(ts_generic_types = "T = unknown", ts_return_type = "Array")] - pub fn slice(&self, env: Env, start: i64, end: i64) -> Result { + pub fn slice(&self, env: Env, start: i64, end: Option) -> Result { let mut js_array = env.create_array(0)?; - for value in self.array.iter().skip(start as usize).take((end - start) as usize) { + let end = (end.unwrap_or(self.length()) - start) as usize; + for value in self.array.iter().skip(start as usize).take(end) { js_array.insert(get_js_unknown_from_value(env, value)?)?; } Ok(js_array) @@ -149,11 +150,31 @@ impl YArray { } #[napi] - pub fn delete(&mut self, index: i64, len: i64) -> Result<()> { - self.array.remove(index as u64, len as u64).map_err(anyhow::Error::from) + pub fn delete(&mut self, index: i64, len: Option) -> Result<()> { + self.array + .remove(index as u64, len.unwrap_or(1) as u64) + .map_err(anyhow::Error::from) + } + + #[napi] + pub fn iter(&self, env: Env) -> YArrayIterator { + YArrayIterator { + array: self.clone(), + env, + current: 0, + } } #[napi] + pub fn to_array(&self, env: Env) -> Result { + let mut js_array = env.create_array(0)?; + for value in self.array.iter() { + js_array.insert(get_js_unknown_from_value(env, value)?)?; + } + Ok(js_array) + } + + #[napi(js_name = "toJSON")] pub fn to_json(&self, env: Env) -> Result { let mut js_array = env.create_array(0)?; for value in self.array.iter() { @@ -161,13 +182,17 @@ impl YArray { } Ok(js_array) } + + // TODO(@darkskygit): impl type based observe #[napi] - pub fn iter(&self, env: Env) -> YArrayIterator { - YArrayIterator { - array: self.clone(), - env, - current: 0, - } + pub fn observe(&mut self, _callback: JsFunction) -> Result<()> { + Ok(()) + } + + // TODO(@darkskygit): impl type based observe + #[napi] + pub fn observe_deep(&mut self, _callback: JsFunction) -> Result<()> { + Ok(()) } } diff --git a/y-octo-node/src/doc.rs b/y-octo-node/src/doc.rs index 1a7a834..3e0ccf1 100644 --- a/y-octo-node/src/doc.rs +++ b/y-octo-node/src/doc.rs @@ -126,6 +126,14 @@ impl YDoc { self.doc.subscribe(Box::new(callback)); Ok(()) } + + #[napi] + pub fn transact(&mut self, callback: JsFunction) -> Result<()> { + callback.call_without_args(None)?; + self.doc.gc()?; + + Ok(()) + } } #[cfg(test)] diff --git a/y-octo-node/src/map.rs b/y-octo-node/src/map.rs index 26c073f..6670f41 100644 --- a/y-octo-node/src/map.rs +++ b/y-octo-node/src/map.rs @@ -1,4 +1,4 @@ -use napi::{Env, JsObject, ValueType}; +use napi::{Env, JsFunction, JsObject, ValueType}; use y_octo::{Any, Map, Value}; use super::*; @@ -105,6 +105,18 @@ impl YMap { } Ok(js_object) } + + // TODO(@darkskygit): impl type based observe + #[napi] + pub fn observe(&mut self, _callback: JsFunction) -> Result<()> { + Ok(()) + } + + // TODO(@darkskygit): impl type based observe + #[napi] + pub fn observe_deep(&mut self, _callback: JsFunction) -> Result<()> { + Ok(()) + } } #[cfg(test)] diff --git a/y-octo-node/tests/map.spec.mts b/y-octo-node/tests/map.spec.mts index f499766..e8f1ae9 100644 --- a/y-octo-node/tests/map.spec.mts +++ b/y-octo-node/tests/map.spec.mts @@ -2,7 +2,7 @@ import assert, { equal, deepEqual } from "node:assert"; import { test } from "node:test"; import * as Y from "yjs"; -import { Doc, YArray, YMap, YText } from "../index"; +import { Doc, Array, Map, Text } from "../index"; test("map test", { concurrency: false }, async (t) => { let client_id: number; @@ -52,7 +52,7 @@ test("map test", { concurrency: false }, async (t) => { sub.set("d", "hello world"); equal(sub.length, 4); - let sub2 = map.get("sub"); + let sub2 = map.get("sub"); assert(sub2); equal(sub2.get("a"), true); equal(sub2.get("b"), false); @@ -131,9 +131,9 @@ test("map test", { concurrency: false }, async (t) => { doc.applyUpdate(Buffer.from(Y.encodeStateAsUpdate(doc2))); let map = doc.getOrCreateMap("map"); - let sub_array = map.get("array"); - let sub_map = map.get("map"); - let sub_text = map.get("text"); + let sub_array = map.get("array"); + let sub_map = map.get("map"); + let sub_text = map.get("text"); assert(sub_array); equal(sub_array.length, 4); diff --git a/y-octo-node/tests/text.spec.mts b/y-octo-node/tests/text.spec.mts index 343d201..f04968e 100644 --- a/y-octo-node/tests/text.spec.mts +++ b/y-octo-node/tests/text.spec.mts @@ -1,7 +1,7 @@ import assert, { equal, deepEqual } from "node:assert"; import { test } from "node:test"; -import { Doc, YText } from "../index"; +import { Doc, Text } from "../index"; test("text test", { concurrency: false }, async (t) => { let client_id: number; @@ -47,7 +47,7 @@ test("text test", { concurrency: false }, async (t) => { sub.insert(2, "c"); equal(sub.toString(), "abc"); - let sub2 = map.get("sub"); + let sub2 = map.get("sub"); assert(sub2); equal(sub2.toString(), "abc"); }); diff --git a/y-octo-node/tests/yjs/testHelper.ts b/y-octo-node/tests/yjs/testHelper.ts new file mode 100644 index 0000000..aec05b2 --- /dev/null +++ b/y-octo-node/tests/yjs/testHelper.ts @@ -0,0 +1,515 @@ +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 object from "lib0/object"; +import * as map from "lib0/map"; +import * as Y from "../../index"; + +if (typeof window !== "undefined") { + // @ts-ignore + window.Y = Y; // eslint-disable-line +} + +/** + * @param {TestYInstance} y // publish message created by `y` to all other online clients + * @param {Uint8Array} m + */ +const broadcastMessage = (y: TestYOctoInstance, m: Uint8Array) => { + if (y.tc.onlineConns.has(y)) { + y.tc.onlineConns.forEach( + (remoteYInstance: { _receive: (arg0: any, arg1: any) => void }) => { + if (remoteYInstance !== y) { + remoteYInstance._receive(m, y); + } + }, + ); + } +}; + +export let useV2 = false; + +export const encV1 = { + encodeStateAsUpdate: Y.encodeStateAsUpdate, + mergeUpdates: Y.mergeUpdates, + applyUpdate: Y.applyUpdate, + // logUpdate: Y.logUpdate, + // updateEventName: /** @type {'update'} */ "update", + // diffUpdate: Y.diffUpdate, +}; + +// export const encV2 = { +// encodeStateAsUpdate: Y.encodeStateAsUpdateV2, +// mergeUpdates: Y.mergeUpdatesV2, +// applyUpdate: Y.applyUpdateV2, +// logUpdate: Y.logUpdateV2, +// updateEventName: /** @type {'updateV2'} */ "updateV2", +// diffUpdate: Y.diffUpdateV2, +// }; + +export let enc = encV1; + +const useV1Encoding = () => { + useV2 = false; + enc = encV1; +}; + +const useV2Encoding = () => { + console.error( + "sync protocol doesnt support v2 protocol yet, fallback to v1 encoding", + ); // @Todo + useV2 = false; + enc = encV1; +}; + +export class TestYOctoInstance extends Y.Doc { + updates: Uint8Array[]; + receiving: Map; + tc: TestConnector; + constructor(testConnector: TestConnector, clientID: number) { + super(clientID); // overwriting clientID + + this.tc = testConnector; + this.receiving = new Map(); + testConnector.allConns.add(this); + this.updates = []; + // set up observe on local model + this.onUpdate((update) => { + if (origin !== testConnector) { + const encoder = encoding.createEncoder(); + syncProtocol.writeUpdate(encoder, update); + broadcastMessage(this, encoding.toUint8Array(encoder)); + } + this.updates.push(update); + }); + this.connect(); + } + + /** + * Disconnect from TestConnector. + */ + disconnect() { + this.receiving = new Map(); + this.tc.onlineConns.delete(this); + } + + /** + * Append yourself to the list of known Y instances in testconnector. + * Also initiate sync with all clients. + */ + connect() { + if (!this.tc.onlineConns.has(this)) { + this.tc.onlineConns.add(this); + const encoder = encoding.createEncoder(); + syncProtocol.writeSyncStep1(encoder, this); + // publish SyncStep1 + broadcastMessage(this, encoding.toUint8Array(encoder)); + this.tc.onlineConns.forEach((remoteYInstance) => { + if (remoteYInstance !== this) { + // remote instance sends instance to this instance + const encoder = encoding.createEncoder(); + syncProtocol.writeSyncStep1(encoder, remoteYInstance); + this._receive(encoding.toUint8Array(encoder), remoteYInstance); + } + }); + } + } + + /** + * Receive a message from another client. This message is only appended to the list of receiving messages. + * TestConnector decides when this client actually reads this message. + */ + _receive(message: Uint8Array, remoteClient: TestYOctoInstance) { + map + .setIfUndefined(this.receiving, remoteClient, () => [] as Uint8Array[]) + .push(message); + } +} + +/** + * Keeps track of TestYInstances. + * + * The TestYInstances add/remove themselves from the list of connections maiained in this object. + * I think it makes sense. Deal with it. + */ +export class TestConnector { + allConns: Set; + onlineConns: Set; + prng: prng.PRNG; + constructor(gen: prng.PRNG) { + this.allConns = new Set(); + this.onlineConns = new Set(); + this.prng = gen; + } + + createY(clientID: number) { + return new TestYOctoInstance(this, clientID); + } + + /** + * Choose random connection and flush a random message from a random sender. + * + * If this function was unable to flush a message, because there are no more messages to flush, it returns false. true otherwise. + */ + flushRandomMessage(): boolean { + const gen = this.prng; + const conns = Array.from(this.onlineConns).filter( + (conn) => conn.receiving.size > 0, + ); + if (conns.length > 0) { + const receiver = prng.oneOf(gen, conns); + const [sender, messages] = prng.oneOf( + gen, + Array.from(receiver.receiving), + ); + const m = messages.shift(); + if (messages.length === 0) { + receiver.receiving.delete(sender); + } + if (m === undefined) { + return this.flushRandomMessage(); + } + const encoder = encoding.createEncoder(); + // console.log('receive (' + sender.userID + '->' + receiver.userID + '):\n', syncProtocol.stringifySyncMessage(decoding.createDecoder(m), receiver)) + // do not publish data created when this function is executed (could be ss2 or update message) + syncProtocol.readSyncMessage( + decoding.createDecoder(m), + encoder, + receiver, + receiver.tc, + ); + if (encoding.length(encoder) > 0) { + // send reply message + sender._receive(encoding.toUint8Array(encoder), receiver); + } + return true; + } + return false; + } + + /** + * @return {boolean} True iff this function actually flushed something + */ + flushAllMessages(): boolean { + let didSomething = false; + while (this.flushRandomMessage()) { + didSomething = true; + } + return didSomething; + } + + reconnectAll() { + this.allConns.forEach((conn: { connect: () => any }) => conn.connect()); + } + + disconnectAll() { + this.allConns.forEach((conn: { disconnect: () => any }) => + conn.disconnect(), + ); + } + + syncAll() { + this.reconnectAll(); + this.flushAllMessages(); + } + + /** + * @return {boolean} Whether it was possible to disconnect a randon connection. + */ + disconnectRandom(): boolean { + if (this.onlineConns.size === 0) { + return false; + } + prng.oneOf(this.prng, Array.from(this.onlineConns)).disconnect(); + return true; + } + + /** + * @return {boolean} Whether it was possible to reconnect a random connection. + */ + reconnectRandom(): boolean { + const reconnectable: TestYOctoInstance[] = []; + this.allConns.forEach((conn: any) => { + if (!this.onlineConns.has(conn)) { + reconnectable.push(conn); + } + }); + if (reconnectable.length === 0) { + return false; + } + prng.oneOf(this.prng, reconnectable).connect(); + return true; + } +} + +type InitResult = { + testConnector: TestConnector; + users: Array; + testObjects?: Array; + array0?: Y.Array; + array1?: Y.Array; + array2?: Y.Array; + map0?: Y.Map; + map1?: Y.Map; + map2?: Y.Map; + map3?: Y.Map; + text0?: Y.Text; + text1?: Y.Text; + text2?: Y.Text; + // xml0: Y.XmlElement; + // xml1: Y.XmlElement; + // xml2: Y.XmlElement; +}; +export const init = ( + tc: { prng: any }, + { users = 5 }: { users?: number } = {}, + initTestObject?: any, +): InitResult => { + const gen = tc.prng; + // choose an encoding approach at random + if (prng.bool(gen)) { + useV2Encoding(); + } else { + useV1Encoding(); + } + + const result: InitResult = { + users: [], + testConnector: new TestConnector(gen), + }; + for (let i = 0; i < users; i++) { + const y = result.testConnector.createY(i); + y.clientId = i; + result.users.push(y); + result["array" + i] = y.getOrCreateArray("array"); + result["map" + i] = y.getOrCreateMap("map"); + // result["xml" + i] = y.get("xml", Y.XmlElement); + result["text" + i] = y.getOrCreateText("text"); + } + result.testConnector.syncAll(); + result.testObjects = result.users.map(initTestObject || (() => null)); + useV1Encoding(); + return result; +}; + +/** + * 1. reconnect and flush all + * 2. user 0 gc + * 3. get type content + * 4. disconnect & reconnect all (so gc is propagated) + * 5. compare os, ds, ss + * + * @param {Array} users + */ +export const compare = (users: any[]) => { + users.forEach((u: { connect: () => any }) => u.connect()); + while (users[0].tc.flushAllMessages()) {} // eslint-disable-line + // For each document, merge all received document updates with Y.mergeUpdates and create a new document which will be added to the list of "users" + // This ensures that mergeUpdates works correctly + const mergedDocs = users.map((user: { updates: any }) => { + const ydoc = new Y.Doc(); + enc.applyUpdate(ydoc, enc.mergeUpdates(user.updates)); + return ydoc; + }); + users.push(.../** @type {any} */ mergedDocs); + const userArrayValues = users.map( + (u: { + getArray: (arg0: string) => { + (): any; + new (): any; + toJSON: { (): any; new (): any }; + }; + }) => u.getArray("array").toJSON(), + ); + const userMapValues = users.map( + (u: { + getMap: (arg0: string) => { + (): any; + new (): any; + toJSON: { (): any; new (): any }; + }; + }) => u.getMap("map").toJSON(), + ); + // const userXmlValues = users.map( + // (u: { + // get: ( + // arg0: string, + // arg1: any, + // ) => { (): any; new (): any; toString: { (): any; new (): any } }; + // }) => u.get("xml", Y.XmlElement).toString(), + // ); + const userTextValues = users.map( + (u: { + getText: (arg0: string) => { + (): any; + new (): any; + toDelta: { (): any; new (): any }; + }; + }) => u.getText("text").toDelta(), + ); + for (const u of users) { + t.assert(u.store.pendingDs === null); + t.assert(u.store.pendingStructs === null); + } + // Test Array iterator + t.compare( + users[0].getArray("array").toArray(), + Array.from(users[0].getArray("array")), + ); + // Test Map iterator + const ymapkeys: any[] = Array.from(users[0].getMap("map").keys()); + t.assert(ymapkeys.length === Object.keys(userMapValues[0]).length); + ymapkeys.forEach((key) => + t.assert(object.hasProperty(userMapValues[0], key)), + ); + + const mapRes: Record = {}; + for (const [k, v] of users[0].getMap("map")) { + mapRes[k] = Y.isAbstractType(v) ? v.toJSON() : v; + } + t.compare(userMapValues[0], mapRes); + // Compare all users + for (let i = 0; i < users.length - 1; i++) { + t.compare(userArrayValues[i].length, users[i].getArray("array").length); + 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: { insert: any }) => + typeof a.insert === "string" ? a.insert : " ", + ) + .join("").length, + users[i].getText("text").length, + ); + t.compare( + userTextValues[i], + userTextValues[i + 1], + "", + (_constructor, a, b) => { + if (Y.isAbstractType(a)) { + t.compare(a.toJSON(), b.toJSON()); + } else if (a !== b) { + t.fail("Deltas dont match"); + } + return true; + }, + ); + t.compare(Y.encodeStateVector(users[i]), Y.encodeStateVector(users[i + 1])); + Y.equalDeleteSets( + 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.map((u: { destroy: () => any }) => u.destroy()); +}; + +export const compareItemIDs = ( + a: { id: any } | null, + b: { id: any } | null, +): boolean => a === b || (a !== null && b != null && Y.compareIDs(a.id, b.id)); + +/** + * @param {import('../src/internals.js').StructStore} ss1 + * @param {import('../src/internals.js').StructStore} ss2 + */ +export const compareStructStores = ( + ss1: { clients: { size: any } }, + ss2: { clients: { size: any; get: (arg0: any) => any } }, +) => { + t.assert(ss1.clients.size === ss2.clients.size); + for (const [client, structs1] of ss1.clients) { + const structs2 = + /** @type {Array} */ ss2.clients.get(client); + t.assert(structs2 !== undefined && structs1.length === structs2.length); + for (let i = 0; i < structs1.length; i++) { + const s1 = structs1[i]; + const s2 = structs2[i]; + // checks for abstract struct + if ( + s1.constructor !== s2.constructor || + !Y.compareIDs(s1.id, s2.id) || + s1.deleted !== s2.deleted || + // @ts-ignore + s1.length !== s2.length + ) { + t.fail("Structs dont match"); + } + if (s1 instanceof Y.Item) { + if ( + !(s2 instanceof Y.Item) || + !( + (s1.left === null && s2.left === null) || + (s1.left !== null && + s2.left !== null && + Y.compareIDs(s1.left.lastId, s2.left.lastId)) + ) || + !compareItemIDs(s1.right, s2.right) || + !Y.compareIDs(s1.origin, s2.origin) || + !Y.compareIDs(s1.rightOrigin, s2.rightOrigin) || + s1.parentSub !== s2.parentSub + ) { + return t.fail("Items dont match"); + } + // make sure that items are connected correctly + t.assert(s1.left === null || s1.left.right === s1); + t.assert(s1.right === null || s1.right.left === s1); + t.assert(s2.left === null || s2.left.right === s2); + t.assert(s2.right === null || s2.right.left === s2); + } + } + } +}; + +/** + * @template T + * @callback InitTestObjectCallback + * @param {TestYInstance} y + * @return {T} + */ + +/** + * @template T + * @param {t.TestCase} tc + * @param {Array} mods + * @param {number} iterations + * @param {InitTestObjectCallback} [initTestObject] + */ +export const applyRandomTests = ( + tc: { prng: any }, + mods: unknown[], + iterations: number, + initTestObject?: any, +) => { + const gen = tc.prng; + const result = init(tc, { users: 5 }, initTestObject); + const { testConnector, users } = result; + for (let i = 0; i < iterations; i++) { + if (prng.int32(gen, 0, 100) <= 2) { + // 2% chance to disconnect/reconnect a random user + if (prng.bool(gen)) { + testConnector.disconnectRandom(); + } else { + testConnector.reconnectRandom(); + } + } else if (prng.int32(gen, 0, 100) <= 1) { + // 1% chance to flush all + testConnector.flushAllMessages(); + } else if (prng.int32(gen, 0, 100) <= 50) { + // 50% chance to flush a random message + testConnector.flushRandomMessage(); + } + const user = prng.int32(gen, 0, users.length - 1); + const test: any = prng.oneOf(gen, mods); + test(users[user], gen, result.testObjects?.[user]); + } + compare(users); + return result; +}; diff --git a/y-octo-node/tests/yjs/yarray.spec.mts b/y-octo-node/tests/yjs/yarray.spec.mts new file mode 100644 index 0000000..126c8be --- /dev/null +++ b/y-octo-node/tests/yjs/yarray.spec.mts @@ -0,0 +1,582 @@ +import { init, compare, applyRandomTests } from "./testHelper.js"; // eslint-disable-line + +import * as Y from "../../index.js"; +import * as t from "lib0/testing"; +import * as prng from "lib0/prng"; +import * as math from "lib0/math"; + +export const testBasicUpdate = (tc: t.TestCase) => { + const doc1 = new Y.Doc(); + const doc2 = new Y.Doc(); + doc1.getOrCreateArray("array").insert(0, ["hi"]); + const update = Y.encodeStateAsUpdate(doc1); + Y.applyUpdate(doc2, update); + t.compare(doc2.getOrCreateArray("array").toArray(), ["hi"]); +}; + +export const testSlice = (tc: t.TestCase) => { + const doc1 = new Y.Doc(); + const arr = doc1.getOrCreateArray("array"); + arr.insert(0, [1, 2, 3]); + t.compareArrays(arr.slice(0), [1, 2, 3]); + t.compareArrays(arr.slice(1), [2, 3]); + t.compareArrays(arr.slice(0, -1), [1, 2]); + arr.insert(0, [0]); + t.compareArrays(arr.slice(0), [0, 1, 2, 3]); + t.compareArrays(arr.slice(0, 2), [0, 1]); +}; + +/** + * @param {t.TestCase} tc + */ +export const testArrayFrom = (tc: t.TestCase) => { + const doc1 = new Y.Doc(); + const db1 = doc1.getOrCreateMap("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 + */ +export const testLengthIssue = (tc: t.TestCase) => { + const doc1 = new Y.Doc(); + const arr = doc1.getOrCreateArray("array"); + arr.push([0, 1, 2, 3]); + arr.delete(0); + arr.insert(0, [0]); + t.assert(arr.length === arr.toArray().length); + doc1.transact(() => { + arr.delete(1); + t.assert(arr.length === arr.toArray().length); + arr.insert(1, [1]); + t.assert(arr.length === arr.toArray().length); + arr.delete(2); + t.assert(arr.length === arr.toArray().length); + arr.insert(2, [2]); + t.assert(arr.length === arr.toArray().length); + }); + t.assert(arr.length === arr.toArray().length); + arr.delete(1); + t.assert(arr.length === arr.toArray().length); + arr.insert(1, [1]); + t.assert(arr.length === arr.toArray().length); +}; + +/** + * Debugging yjs#314 + */ +export const testLengthIssue2 = (tc: t.TestCase) => { + const doc = new Y.Doc(); + const next = doc.createArray(); + doc.transact(() => { + next.insert(0, ["group2"]); + }); + doc.transact(() => { + next.insert(1, ["rectangle3"]); + }); + doc.transact(() => { + next.delete(0); + next.insert(0, ["rectangle3"]); + }); + next.delete(1); + doc.transact(() => { + next.insert(1, ["ellipse4"]); + }); + doc.transact(() => { + next.insert(2, ["ellipse3"]); + }); + doc.transact(() => { + next.insert(3, ["ellipse2"]); + }); + doc.transact(() => { + doc.transact(() => { + t.fails(() => { + next.insert(5, ["rectangle2"]); + }); + next.insert(4, ["rectangle2"]); + }); + doc.transact(() => { + // this should not throw an error message + next.delete(4); + }); + }); + console.log(next.toArray()); +}; + +export const testDeleteInsert = (tc: t.TestCase) => { + const { users, array0 } = init(tc, { users: 2 }); + t.assert(array0); + array0.delete(0, 0); + t.describe("Does not throw when deleting zero elements with position 0"); + t.fails(() => { + array0.delete(1, 1); + }); + array0.insert(0, ["A"]); + array0.delete(1, 0); + t.describe( + "Does not throw when deleting zero elements with valid position 1", + ); + compare(users); +}; + +export const testInsertThreeElementsTryRegetProperty = (tc: t.TestCase) => { + const { testConnector, users, array0, array1 } = init(tc, { users: 2 }); + t.assert(array0); + t.assert(array1); + array0.insert(0, [1, true, false]); + t.compare(array0.toJSON(), [1, true, false], ".toJSON() works"); + testConnector.flushAllMessages(); + t.compare(array1.toJSON(), [1, true, false], ".toJSON() works after sync"); + compare(users); +}; + +export const testConcurrentInsertWithThreeConflicts = (tc: t.TestCase) => { + const { users, array0, array1, array2 } = init(tc, { users: 3 }); + t.assert(array0); + t.assert(array1); + t.assert(array2); + array0.insert(0, [0]); + array1.insert(0, [1]); + array2.insert(0, [2]); + compare(users); +}; + +export const testConcurrentInsertDeleteWithThreeConflicts = ( + tc: t.TestCase, +) => { + const { testConnector, users, array0, array1, array2 } = init(tc, { + users: 3, + }); + t.assert(array0); + t.assert(array1); + t.assert(array2); + array0.insert(0, ["x", "y", "z"]); + testConnector.flushAllMessages(); + array0.insert(1, [0]); + array1.delete(0); + array1.delete(1, 1); + array2.insert(1, [2]); + compare(users); +}; + +export const testInsertionsInLateSync = (tc: t.TestCase) => { + const { testConnector, users, array0, array1, array2 } = init(tc, { + users: 3, + }); + t.assert(array0); + t.assert(array1); + t.assert(array2); + array0.insert(0, ["x", "y"]); + testConnector.flushAllMessages(); + users[1].disconnect(); + users[2].disconnect(); + array0.insert(1, ["user0"]); + array1.insert(1, ["user1"]); + array2.insert(1, ["user2"]); + users[1].connect(); + users[2].connect(); + testConnector.flushAllMessages(); + compare(users); +}; + +export const testDisconnectReallyPreventsSendingMessages = (tc: t.TestCase) => { + const { testConnector, users, array0, array1 } = init(tc, { users: 3 }); + t.assert(array0); + t.assert(array1); + array0.insert(0, ["x", "y"]); + testConnector.flushAllMessages(); + users[1].disconnect(); + users[2].disconnect(); + array0.insert(1, ["user0"]); + array1.insert(1, ["user1"]); + t.compare(array0.toJSON(), ["x", "user0", "y"]); + t.compare(array1.toJSON(), ["x", "user1", "y"]); + users[1].connect(); + users[2].connect(); + compare(users); +}; + +export const testDeletionsInLateSync = (tc: t.TestCase) => { + const { testConnector, users, array0, array1 } = init(tc, { users: 2 }); + t.assert(array0); + t.assert(array1); + array0.insert(0, ["x", "y"]); + testConnector.flushAllMessages(); + users[1].disconnect(); + array1.delete(1, 1); + array0.delete(0, 2); + users[1].connect(); + compare(users); +}; + +export const testInsertThenMergeDeleteOnSync = (tc: t.TestCase) => { + const { testConnector, users, array0, array1 } = init(tc, { users: 2 }); + t.assert(array0); + t.assert(array1); + array0.insert(0, ["x", "y", "z"]); + testConnector.flushAllMessages(); + users[0].disconnect(); + array1.delete(0, 3); + users[0].connect(); + compare(users); +}; + +export const testInsertAndDeleteEvents = (tc: t.TestCase) => { + const { array0, users } = init(tc, { users: 2 }); + t.assert(array0); + let event: Record | null = null; + array0.observe((e) => { + event = e; + }); + array0.insert(0, [0, 1, 2]); + t.assert(event !== null); + event = null; + array0.delete(0); + t.assert(event !== null); + event = null; + array0.delete(0, 2); + t.assert(event !== null); + event = null; + compare(users); +}; + +export const testNestedObserverEvents = (tc: t.TestCase) => { + const { array0, users } = init(tc, { users: 2 }); + t.assert(array0); + const vals: number[] = []; + array0.observe((e) => { + if (array0.length === 1) { + // inserting, will call this observer again + // we expect that this observer is called after this event handler finishedn + array0.insert(1, [1]); + vals.push(0); + } else { + // this should be called the second time an element is inserted (above case) + vals.push(1); + } + }); + array0.insert(0, [0]); + t.compareArrays(vals, [0, 1]); + t.compareArrays(array0.toArray(), [0, 1]); + compare(users); +}; + +export const testInsertAndDeleteEventsForTypes = (tc: t.TestCase) => { + const { array0, users } = init(tc, { users: 2 }); + t.assert(array0); + let event: Record | null = null; + array0.observe((e) => { + event = e; + }); + array0.insert(0, [new Y.Array()]); + t.assert(event !== null); + event = null; + array0.delete(0); + t.assert(event !== null); + event = null; + compare(users); +}; + +/** + * This issue has been reported in https://discuss.yjs.dev/t/order-in-which-events-yielded-by-observedeep-should-be-applied/261/2 + * + * Deep observers generate multiple events. When an array added at item at, say, position 0, + * and item 1 changed then the array-add event should fire first so that the change event + * path is correct. A array binding might lead to an inconsistent state otherwise. + */ +export const testObserveDeepEventOrder = (tc: t.TestCase) => { + const { array0, users } = init(tc, { users: 2 }); + t.assert(array0); + let events: any[] = []; + array0.observeDeep((e) => { + events = e; + }); + array0.insert(0, [new Y.Map()]); + users[0].transact(() => { + array0.get(0).set("a", "a"); + array0.insert(0, [0]); + }); + for (let i = 1; i < events.length; i++) { + t.assert( + events[i - 1].path.length <= events[i].path.length, + "path size increases, fire top-level events first", + ); + } +}; + +/** + * Correct index when computing event.path in observeDeep - https://github.com/yjs/yjs/issues/457 + */ +export const testObservedeepIndexes = (_tc: t.TestCase) => { + const doc = new Y.Doc(); + const map = doc.createMap(); + // 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()]); + let eventPath: any[] = []; + 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]); +}; + +export const testChangeEvent = (tc: t.TestCase) => { + const { array0, users } = init(tc, { users: 2 }); + t.assert(array0); + let changes: any = null; + array0.observe((e) => { + changes = e.changes; + }); + 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; + array0.delete(0, 2); + t.assert( + changes !== null && changes.added.size === 0 && changes.deleted.size === 2, + ); + t.compare(changes.delta, [{ delete: 2 }]); + changes = null; + 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] }]); + compare(users); +}; + +export const testInsertAndDeleteEventsForTypes2 = (tc: t.TestCase) => { + const { array0, users } = init(tc, { users: 2 }); + t.assert(array0); + const events: Record[] = []; + array0.observe((e) => { + events.push(e); + }); + array0.insert(0, ["hi", new Y.Map()]); + t.assert( + events.length === 1, + "Event is triggered exactly once for insertion of two elements", + ); + array0.delete(1); + t.assert(events.length === 2, "Event is triggered exactly once for deletion"); + compare(users); +}; + +/** + * This issue has been reported here https://github.com/yjs/yjs/issues/155 + * @param {t.TestCase} tc + */ +export const testNewChildDoesNotEmitEventInTransaction = (tc: t.TestCase) => { + const { array0, users } = init(tc, { users: 2 }); + t.assert(array0); + let fired = false; + users[0].transact(() => { + const newMap = new Y.Map(); + newMap.observe(() => { + fired = true; + }); + array0.insert(0, [newMap]); + newMap.set("tst", 42); + }); + t.assert(!fired, "Event does not trigger"); +}; + +export const testGarbageCollector = (tc: t.TestCase) => { + const { testConnector, users, array0 } = init(tc, { users: 3 }); + t.assert(array0); + array0.insert(0, ["x", "y", "z"]); + testConnector.flushAllMessages(); + users[0].disconnect(); + array0.delete(0, 3); + users[0].connect(); + testConnector.flushAllMessages(); + compare(users); +}; + +/** + * @param {t.TestCase} tc + */ +export const testEventTargetIsSetCorrectlyOnLocal = (tc: t.TestCase) => { + const { array0, users } = init(tc, { users: 3 }); + t.assert(array0); + let event: any; + array0.observe((e) => { + event = e; + }); + array0.insert(0, ["stuff"]); + t.assert(event.target === array0, '"target" property is set correctly'); + compare(users); +}; + +export const testEventTargetIsSetCorrectlyOnRemote = (tc: t.TestCase) => { + const { testConnector, array0, array1, users } = init(tc, { users: 3 }); + t.assert(array0); + t.assert(array1); + let event: any; + array0.observe((e) => { + event = e; + }); + array1.insert(0, ["stuff"]); + testConnector.flushAllMessages(); + t.assert(event.target === array0, '"target" property is set correctly'); + compare(users); +}; + +export const testIteratingArrayContainingTypes = (tc: t.TestCase) => { + const y = new Y.Doc(); + const arr = y.getOrCreateArray("arr"); + const numItems = 10; + for (let i = 0; i < numItems; i++) { + const map = new Y.Map(); + map.set("value", i); + arr.push([map]); + } + let cnt = 0; + for (const item of arr.iter()) { + t.assert(item.get("value") === cnt++, "value is correct"); + } + y.destroy(); +}; + +let _uniqueNumber = 0; +const getUniqueNumber = () => _uniqueNumber++; + +const arrayTransactions: Array< + (arg0: Y.Doc, arg1: prng.PRNG, arg2: any) => void +> = [ + function insert(user: Y.Doc, gen: prng.PRNG) { + const yarray = user.getOrCreateArray("array"); + const uniqueNumber = getUniqueNumber(); + const content: number[] = []; + const len = prng.int32(gen, 1, 4); + for (let i = 0; i < len; i++) { + content.push(uniqueNumber); + } + const pos = prng.int32(gen, 0, yarray.length); + const oldContent = yarray.toArray(); + yarray.insert(pos, content); + oldContent.splice(pos, 0, ...content); + t.compareArrays(yarray.toArray(), oldContent); // we want to make sure that fastSearch markers insert at the correct position + }, + function insertTypeArray(user: Y.Doc, gen: prng.PRNG) { + const yarray = user.getOrCreateArray("array"); + const pos = prng.int32(gen, 0, yarray.length); + yarray.insert(pos, [new Y.Array()]); + const array2 = yarray.get(pos); + array2.insert(0, [1, 2, 3, 4]); + }, + function insertTypeMap(user: Y.Doc, gen: prng.PRNG) { + const yarray = user.getOrCreateArray("array"); + const pos = prng.int32(gen, 0, yarray.length); + yarray.insert(pos, [new Y.Map()]); + const map = yarray.get(pos); + map.set("someprop", 42); + map.set("someprop", 43); + map.set("someprop", 44); + }, + function insertTypeNull(user: Y.Doc, gen: prng.PRNG) { + const yarray = user.getOrCreateArray("array"); + const pos = prng.int32(gen, 0, yarray.length); + yarray.insert(pos, [null]); + }, + function _delete(user: Y.Doc, gen: prng.PRNG) { + const yarray = user.getOrCreateArray("array"); + const length = yarray.length; + if (length > 0) { + let somePos = prng.int32(gen, 0, length - 1); + let delLength = prng.int32(gen, 1, math.min(2, length - somePos)); + if (prng.bool(gen)) { + const type = yarray.get(somePos); + if (type instanceof Y.Array && type.length > 0) { + somePos = prng.int32(gen, 0, type.length - 1); + delLength = prng.int32(gen, 0, math.min(2, type.length - somePos)); + type.delete(somePos, delLength); + } + } else { + const oldContent = yarray.toArray(); + yarray.delete(somePos, delLength); + oldContent.splice(somePos, delLength); + t.compareArrays(yarray.toArray(), oldContent); + } + } + }, +]; + +/** + * @param {t.TestCase} tc + */ +export const testRepeatGeneratingYarrayTests6 = (tc: t.TestCase) => { + applyRandomTests(tc, arrayTransactions, 6); +}; + +export const testRepeatGeneratingYarrayTests40 = (tc: t.TestCase) => { + applyRandomTests(tc, arrayTransactions, 40); +}; + +export const testRepeatGeneratingYarrayTests42 = (tc: t.TestCase) => { + applyRandomTests(tc, arrayTransactions, 42); +}; + +export const testRepeatGeneratingYarrayTests43 = (tc: t.TestCase) => { + applyRandomTests(tc, arrayTransactions, 43); +}; + +export const testRepeatGeneratingYarrayTests44 = (tc: t.TestCase) => { + applyRandomTests(tc, arrayTransactions, 44); +}; + +export const testRepeatGeneratingYarrayTests45 = (tc: t.TestCase) => { + applyRandomTests(tc, arrayTransactions, 45); +}; + +export const testRepeatGeneratingYarrayTests46 = (tc: t.TestCase) => { + applyRandomTests(tc, arrayTransactions, 46); +}; + +export const testRepeatGeneratingYarrayTests300 = (tc: t.TestCase) => { + applyRandomTests(tc, arrayTransactions, 300); +}; + +export const testRepeatGeneratingYarrayTests400 = (tc: t.TestCase) => { + applyRandomTests(tc, arrayTransactions, 400); +}; + +export const testRepeatGeneratingYarrayTests500 = (tc: t.TestCase) => { + applyRandomTests(tc, arrayTransactions, 500); +}; + +export const testRepeatGeneratingYarrayTests600 = (tc: t.TestCase) => { + applyRandomTests(tc, arrayTransactions, 600); +}; + +export const testRepeatGeneratingYarrayTests1000 = (tc: t.TestCase) => { + applyRandomTests(tc, arrayTransactions, 1000); +}; + +export const testRepeatGeneratingYarrayTests1800 = (tc: t.TestCase) => { + applyRandomTests(tc, arrayTransactions, 1800); +}; + +export const testRepeatGeneratingYarrayTests3000 = (tc: t.TestCase) => { + t.skip(!t.production); + applyRandomTests(tc, arrayTransactions, 3000); +}; + +export const testRepeatGeneratingYarrayTests5000 = (tc: t.TestCase) => { + t.skip(!t.production); + applyRandomTests(tc, arrayTransactions, 5000); +}; + +export const testRepeatGeneratingYarrayTests30000 = (tc: t.TestCase) => { + t.skip(!t.production); + applyRandomTests(tc, arrayTransactions, 30000); +}; From d47d6e807474e5db2e665037bcf0bfa7d42e994d Mon Sep 17 00:00:00 2001 From: DarkSky Date: Wed, 3 Jul 2024 15:44:17 +0800 Subject: [PATCH 10/75] test: integrate yarray test --- y-octo-node/package.json | 1 + y-octo-node/scripts/run-test.mts | 10 +- y-octo-node/src/array.rs | 17 +- y-octo-node/src/doc.rs | 1 - y-octo-node/tests/yjs/yarray.spec.mts | 1131 +++++++++++++------------ y-octo-node/tsconfig.json | 2 +- yarn.lock | 14 +- 7 files changed, 607 insertions(+), 569 deletions(-) diff --git a/y-octo-node/package.json b/y-octo-node/package.json index 1f404f8..bbb8a9f 100644 --- a/y-octo-node/package.json +++ b/y-octo-node/package.json @@ -27,6 +27,7 @@ "prompts": "^2.4.2", "tsx": "^4.16.2", "typescript": "^5.1.6", + "y-protocols": "^1.0.6", "yjs": "^13.6.18" }, "engines": { diff --git a/y-octo-node/scripts/run-test.mts b/y-octo-node/scripts/run-test.mts index 05dba71..3122356 100755 --- a/y-octo-node/scripts/run-test.mts +++ b/y-octo-node/scripts/run-test.mts @@ -11,6 +11,10 @@ import pkg from "../package.json" assert { type: "json" }; const root = fileURLToPath(new URL("..", import.meta.url)); const testDir = resolve(root, "tests"); const files = await readdir(testDir); +const yjsTestDir = resolve(testDir, "yjs"); +const yjsFiles = (await readdir(yjsTestDir)).filter((f) => + f.endsWith(".spec.mts"), +); const watchMode = process.argv.includes("--watch"); @@ -30,7 +34,11 @@ const env = { if (process.argv[2] === "all") { const cp = spawn( "node", - [...sharedArgs, ...files.map((f) => resolve(testDir, f))], + [ + ...sharedArgs, + ...files.map((f) => resolve(testDir, f)), + ...yjsFiles.map((f) => resolve(yjsTestDir, f)), + ], { cwd: root, env, diff --git a/y-octo-node/src/array.rs b/y-octo-node/src/array.rs index 80bbfbb..52d4129 100644 --- a/y-octo-node/src/array.rs +++ b/y-octo-node/src/array.rs @@ -1,4 +1,4 @@ -use napi::{bindgen_prelude::Array as JsArray, Env, JsFunction, JsUnknown, ValueType}; +use napi::{bindgen_prelude::Array as JsArray, iterator::Generator, Env, JsFunction, JsUnknown, ValueType}; use y_octo::{Any, Array, Value}; use super::*; @@ -50,7 +50,20 @@ impl YArray { #[napi(ts_generic_types = "T = unknown", ts_return_type = "Array")] pub fn slice(&self, env: Env, start: i64, end: Option) -> Result { let mut js_array = env.create_array(0)?; - let end = (end.unwrap_or(self.length()) - start) as usize; + let end = end + .map(|end| { + if end.is_negative() { + self.length() + end + } else { + let end = end - start; + if end.is_negative() { + 0 + } else { + end + } + } + }) + .unwrap_or(self.length() - start) as usize; for value in self.array.iter().skip(start as usize).take(end) { js_array.insert(get_js_unknown_from_value(env, value)?)?; } diff --git a/y-octo-node/src/doc.rs b/y-octo-node/src/doc.rs index 3e0ccf1..74b0bd8 100644 --- a/y-octo-node/src/doc.rs +++ b/y-octo-node/src/doc.rs @@ -130,7 +130,6 @@ impl YDoc { #[napi] pub fn transact(&mut self, callback: JsFunction) -> Result<()> { callback.call_without_args(None)?; - self.doc.gc()?; Ok(()) } diff --git a/y-octo-node/tests/yjs/yarray.spec.mts b/y-octo-node/tests/yjs/yarray.spec.mts index 126c8be..a55335d 100644 --- a/y-octo-node/tests/yjs/yarray.spec.mts +++ b/y-octo-node/tests/yjs/yarray.spec.mts @@ -1,582 +1,587 @@ -import { init, compare, applyRandomTests } from "./testHelper.js"; // eslint-disable-line +import assert, { equal, deepEqual, doesNotThrow } from "node:assert"; +import { test } from "node:test"; -import * as Y from "../../index.js"; +import { init, compare, applyRandomTests } from "./testHelper"; + +import * as Y from "../../yocto"; import * as t from "lib0/testing"; import * as prng from "lib0/prng"; import * as math from "lib0/math"; -export const testBasicUpdate = (tc: t.TestCase) => { - const doc1 = new Y.Doc(); - const doc2 = new Y.Doc(); - doc1.getOrCreateArray("array").insert(0, ["hi"]); - const update = Y.encodeStateAsUpdate(doc1); - Y.applyUpdate(doc2, update); - t.compare(doc2.getOrCreateArray("array").toArray(), ["hi"]); -}; - -export const testSlice = (tc: t.TestCase) => { - const doc1 = new Y.Doc(); - const arr = doc1.getOrCreateArray("array"); - arr.insert(0, [1, 2, 3]); - t.compareArrays(arr.slice(0), [1, 2, 3]); - t.compareArrays(arr.slice(1), [2, 3]); - t.compareArrays(arr.slice(0, -1), [1, 2]); - arr.insert(0, [0]); - t.compareArrays(arr.slice(0), [0, 1, 2, 3]); - t.compareArrays(arr.slice(0, 2), [0, 1]); -}; - -/** - * @param {t.TestCase} tc - */ -export const testArrayFrom = (tc: t.TestCase) => { - const doc1 = new Y.Doc(); - const db1 = doc1.getOrCreateMap("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 - */ -export const testLengthIssue = (tc: t.TestCase) => { - const doc1 = new Y.Doc(); - const arr = doc1.getOrCreateArray("array"); - arr.push([0, 1, 2, 3]); - arr.delete(0); - arr.insert(0, [0]); - t.assert(arr.length === arr.toArray().length); - doc1.transact(() => { - arr.delete(1); - t.assert(arr.length === arr.toArray().length); - arr.insert(1, [1]); - t.assert(arr.length === arr.toArray().length); - arr.delete(2); - t.assert(arr.length === arr.toArray().length); - arr.insert(2, [2]); - t.assert(arr.length === arr.toArray().length); - }); - t.assert(arr.length === arr.toArray().length); - arr.delete(1); - t.assert(arr.length === arr.toArray().length); - arr.insert(1, [1]); - t.assert(arr.length === arr.toArray().length); -}; - -/** - * Debugging yjs#314 - */ -export const testLengthIssue2 = (tc: t.TestCase) => { - const doc = new Y.Doc(); - const next = doc.createArray(); - doc.transact(() => { - next.insert(0, ["group2"]); - }); - doc.transact(() => { - next.insert(1, ["rectangle3"]); +test("yjs array test", { concurrency: false }, async (t) => { + await t.test("testBasicUpdate", () => { + const doc1 = new Y.Doc(); + const doc2 = new Y.Doc(); + doc1.getOrCreateArray("array").insert(0, ["hi"]); + const update = Y.encodeStateAsUpdate(doc1); + Y.applyUpdate(doc2, update); + deepEqual(doc2.getOrCreateArray("array").toArray(), ["hi"]); }); - doc.transact(() => { - next.delete(0); - next.insert(0, ["rectangle3"]); - }); - next.delete(1); - doc.transact(() => { - next.insert(1, ["ellipse4"]); - }); - doc.transact(() => { - next.insert(2, ["ellipse3"]); + + await t.test("testSlice", () => { + const doc1 = new Y.Doc(); + const arr = doc1.getOrCreateArray("array"); + arr.insert(0, [1, 2, 3]); + deepEqual(arr.slice(0), [1, 2, 3]); + deepEqual(arr.slice(1), [2, 3]); + deepEqual(arr.slice(0, -1), [1, 2]); + arr.insert(0, [0]); + deepEqual(arr.slice(0), [0, 1, 2, 3]); + deepEqual(arr.slice(0, 2), [0, 1]); }); - doc.transact(() => { - next.insert(3, ["ellipse2"]); + + const testArrayFrom = () => { + const doc1 = new Y.Doc(); + const db1 = doc1.getOrCreateMap("root"); + const nestedArray1 = Y.Array.from([0, 1, 2]); + db1.set("array", nestedArray1); + deepEqual(nestedArray1.toArray(), [0, 1, 2]); + }; + + /** + * Debugging yjs#297 - a critical bug connected to the search-marker approach + */ + await t.test("testLengthIssue", () => { + const doc1 = new Y.Doc(); + const arr = doc1.getOrCreateArray("array"); + arr.push([0, 1, 2, 3]); + arr.delete(0); + arr.insert(0, [0]); + equal(arr.length, arr.toArray().length); + doc1.transact(() => { + arr.delete(1); + equal(arr.length, arr.toArray().length); + arr.insert(1, [1]); + equal(arr.length, arr.toArray().length); + arr.delete(2); + equal(arr.length, arr.toArray().length); + arr.insert(2, [2]); + equal(arr.length, arr.toArray().length); + }); + equal(arr.length, arr.toArray().length); + arr.delete(1); + equal(arr.length, arr.toArray().length); + arr.insert(1, [1]); + equal(arr.length, arr.toArray().length); }); - doc.transact(() => { + + /** + * Debugging yjs#314 + */ + await t.test("testLengthIssue2", () => { + const doc = new Y.Doc(); + const next = doc.createArray(); doc.transact(() => { - t.fails(() => { - next.insert(5, ["rectangle2"]); - }); - next.insert(4, ["rectangle2"]); + next.insert(0, ["group2"]); }); doc.transact(() => { - // this should not throw an error message - next.delete(4); + next.insert(1, ["rectangle3"]); }); + doc.transact(() => { + next.delete(0); + next.insert(0, ["rectangle3"]); + }); + next.delete(1); + doc.transact(() => { + next.insert(1, ["ellipse4"]); + }); + doc.transact(() => { + next.insert(2, ["ellipse3"]); + }); + doc.transact(() => { + next.insert(3, ["ellipse2"]); + }); + doc.transact(() => { + doc.transact(() => { + doesNotThrow(() => { + next.insert(5, ["rectangle2"]); + }); + next.insert(4, ["rectangle2"]); + }); + doc.transact(() => { + // this should not throw an error message + next.delete(4); + }); + }); + console.log(next.toArray()); }); - console.log(next.toArray()); -}; - -export const testDeleteInsert = (tc: t.TestCase) => { - const { users, array0 } = init(tc, { users: 2 }); - t.assert(array0); - array0.delete(0, 0); - t.describe("Does not throw when deleting zero elements with position 0"); - t.fails(() => { - array0.delete(1, 1); - }); - array0.insert(0, ["A"]); - array0.delete(1, 0); - t.describe( - "Does not throw when deleting zero elements with valid position 1", - ); - compare(users); -}; - -export const testInsertThreeElementsTryRegetProperty = (tc: t.TestCase) => { - const { testConnector, users, array0, array1 } = init(tc, { users: 2 }); - t.assert(array0); - t.assert(array1); - array0.insert(0, [1, true, false]); - t.compare(array0.toJSON(), [1, true, false], ".toJSON() works"); - testConnector.flushAllMessages(); - t.compare(array1.toJSON(), [1, true, false], ".toJSON() works after sync"); - compare(users); -}; - -export const testConcurrentInsertWithThreeConflicts = (tc: t.TestCase) => { - const { users, array0, array1, array2 } = init(tc, { users: 3 }); - t.assert(array0); - t.assert(array1); - t.assert(array2); - array0.insert(0, [0]); - array1.insert(0, [1]); - array2.insert(0, [2]); - compare(users); -}; - -export const testConcurrentInsertDeleteWithThreeConflicts = ( - tc: t.TestCase, -) => { - const { testConnector, users, array0, array1, array2 } = init(tc, { - users: 3, - }); - t.assert(array0); - t.assert(array1); - t.assert(array2); - array0.insert(0, ["x", "y", "z"]); - testConnector.flushAllMessages(); - array0.insert(1, [0]); - array1.delete(0); - array1.delete(1, 1); - array2.insert(1, [2]); - compare(users); -}; - -export const testInsertionsInLateSync = (tc: t.TestCase) => { - const { testConnector, users, array0, array1, array2 } = init(tc, { - users: 3, - }); - t.assert(array0); - t.assert(array1); - t.assert(array2); - array0.insert(0, ["x", "y"]); - testConnector.flushAllMessages(); - users[1].disconnect(); - users[2].disconnect(); - array0.insert(1, ["user0"]); - array1.insert(1, ["user1"]); - array2.insert(1, ["user2"]); - users[1].connect(); - users[2].connect(); - testConnector.flushAllMessages(); - compare(users); -}; - -export const testDisconnectReallyPreventsSendingMessages = (tc: t.TestCase) => { - const { testConnector, users, array0, array1 } = init(tc, { users: 3 }); - t.assert(array0); - t.assert(array1); - array0.insert(0, ["x", "y"]); - testConnector.flushAllMessages(); - users[1].disconnect(); - users[2].disconnect(); - array0.insert(1, ["user0"]); - array1.insert(1, ["user1"]); - t.compare(array0.toJSON(), ["x", "user0", "y"]); - t.compare(array1.toJSON(), ["x", "user1", "y"]); - users[1].connect(); - users[2].connect(); - compare(users); -}; - -export const testDeletionsInLateSync = (tc: t.TestCase) => { - const { testConnector, users, array0, array1 } = init(tc, { users: 2 }); - t.assert(array0); - t.assert(array1); - array0.insert(0, ["x", "y"]); - testConnector.flushAllMessages(); - users[1].disconnect(); - array1.delete(1, 1); - array0.delete(0, 2); - users[1].connect(); - compare(users); -}; - -export const testInsertThenMergeDeleteOnSync = (tc: t.TestCase) => { - const { testConnector, users, array0, array1 } = init(tc, { users: 2 }); - t.assert(array0); - t.assert(array1); - array0.insert(0, ["x", "y", "z"]); - testConnector.flushAllMessages(); - users[0].disconnect(); - array1.delete(0, 3); - users[0].connect(); - compare(users); -}; - -export const testInsertAndDeleteEvents = (tc: t.TestCase) => { - const { array0, users } = init(tc, { users: 2 }); - t.assert(array0); - let event: Record | null = null; - array0.observe((e) => { - event = e; - }); - array0.insert(0, [0, 1, 2]); - t.assert(event !== null); - event = null; - array0.delete(0); - t.assert(event !== null); - event = null; - array0.delete(0, 2); - t.assert(event !== null); - event = null; - compare(users); -}; - -export const testNestedObserverEvents = (tc: t.TestCase) => { - const { array0, users } = init(tc, { users: 2 }); - t.assert(array0); - const vals: number[] = []; - array0.observe((e) => { - if (array0.length === 1) { - // inserting, will call this observer again - // we expect that this observer is called after this event handler finishedn - array0.insert(1, [1]); - vals.push(0); - } else { - // this should be called the second time an element is inserted (above case) - vals.push(1); - } - }); - array0.insert(0, [0]); - t.compareArrays(vals, [0, 1]); - t.compareArrays(array0.toArray(), [0, 1]); - compare(users); -}; - -export const testInsertAndDeleteEventsForTypes = (tc: t.TestCase) => { - const { array0, users } = init(tc, { users: 2 }); - t.assert(array0); - let event: Record | null = null; - array0.observe((e) => { - event = e; - }); - array0.insert(0, [new Y.Array()]); - t.assert(event !== null); - event = null; - array0.delete(0); - t.assert(event !== null); - event = null; - compare(users); -}; - -/** - * This issue has been reported in https://discuss.yjs.dev/t/order-in-which-events-yielded-by-observedeep-should-be-applied/261/2 - * - * Deep observers generate multiple events. When an array added at item at, say, position 0, - * and item 1 changed then the array-add event should fire first so that the change event - * path is correct. A array binding might lead to an inconsistent state otherwise. - */ -export const testObserveDeepEventOrder = (tc: t.TestCase) => { - const { array0, users } = init(tc, { users: 2 }); - t.assert(array0); - let events: any[] = []; - array0.observeDeep((e) => { - events = e; - }); - array0.insert(0, [new Y.Map()]); - users[0].transact(() => { - array0.get(0).set("a", "a"); + + const testDeleteInsert = () => { + const { users, array0 } = init(tc, { users: 2 }); + assert(array0); + array0.delete(0, 0); + doesNotThrow(() => { + array0.delete(1, 1); + }, "Does not throw when deleting zero elements with position 0"); + array0.insert(0, ["A"]); + doesNotThrow(() => { + array0.delete(1, 0); + }, "Does not throw when deleting zero elements with valid position 1"); + compare(users); + }; + + const testInsertThreeElementsTryRegetProperty = (tc: t.TestCase) => { + const { testConnector, users, array0, array1 } = init(tc, { users: 2 }); + assert(array0); + assert(array1); + array0.insert(0, [1, true, false]); + deepEqual(array0.toJSON(), [1, true, false], ".toJSON() works"); + testConnector.flushAllMessages(); + deepEqual(array1.toJSON(), [1, true, false], ".toJSON() works after sync"); + compare(users); + }; + + const testConcurrentInsertWithThreeConflicts = (tc: t.TestCase) => { + const { users, array0, array1, array2 } = init(tc, { users: 3 }); + assert(array0); + assert(array1); + assert(array2); array0.insert(0, [0]); - }); - for (let i = 1; i < events.length; i++) { - t.assert( - events[i - 1].path.length <= events[i].path.length, - "path size increases, fire top-level events first", + array1.insert(0, [1]); + array2.insert(0, [2]); + compare(users); + }; + + const testConcurrentInsertDeleteWithThreeConflicts = (tc: t.TestCase) => { + const { testConnector, users, array0, array1, array2 } = init(tc, { + users: 3, + }); + assert(array0); + assert(array1); + assert(array2); + array0.insert(0, ["x", "y", "z"]); + testConnector.flushAllMessages(); + array0.insert(1, [0]); + array1.delete(0); + array1.delete(1, 1); + array2.insert(1, [2]); + compare(users); + }; + + const testInsertionsInLateSync = (tc: t.TestCase) => { + const { testConnector, users, array0, array1, array2 } = init(tc, { + users: 3, + }); + assert(array0); + assert(array1); + assert(array2); + array0.insert(0, ["x", "y"]); + testConnector.flushAllMessages(); + users[1].disconnect(); + users[2].disconnect(); + array0.insert(1, ["user0"]); + array1.insert(1, ["user1"]); + array2.insert(1, ["user2"]); + users[1].connect(); + users[2].connect(); + testConnector.flushAllMessages(); + compare(users); + }; + + const testDisconnectReallyPreventsSendingMessages = (tc: t.TestCase) => { + const { testConnector, users, array0, array1 } = init(tc, { users: 3 }); + assert(array0); + assert(array1); + array0.insert(0, ["x", "y"]); + testConnector.flushAllMessages(); + users[1].disconnect(); + users[2].disconnect(); + array0.insert(1, ["user0"]); + array1.insert(1, ["user1"]); + deepEqual(array0.toJSON(), ["x", "user0", "y"]); + deepEqual(array1.toJSON(), ["x", "user1", "y"]); + users[1].connect(); + users[2].connect(); + compare(users); + }; + + const testDeletionsInLateSync = (tc: t.TestCase) => { + const { testConnector, users, array0, array1 } = init(tc, { users: 2 }); + assert(array0); + assert(array1); + array0.insert(0, ["x", "y"]); + testConnector.flushAllMessages(); + users[1].disconnect(); + array1.delete(1, 1); + array0.delete(0, 2); + users[1].connect(); + compare(users); + }; + + const testInsertThenMergeDeleteOnSync = (tc: t.TestCase) => { + const { testConnector, users, array0, array1 } = init(tc, { users: 2 }); + assert(array0); + assert(array1); + array0.insert(0, ["x", "y", "z"]); + testConnector.flushAllMessages(); + users[0].disconnect(); + array1.delete(0, 3); + users[0].connect(); + compare(users); + }; + + const testInsertAndDeleteEvents = (tc: t.TestCase) => { + const { array0, users } = init(tc, { users: 2 }); + assert(array0); + let event: Record | null = null; + array0.observe((e) => { + event = e; + }); + array0.insert(0, [0, 1, 2]); + assert(event !== null); + event = null; + array0.delete(0); + assert(event !== null); + event = null; + array0.delete(0, 2); + assert(event !== null); + event = null; + compare(users); + }; + + const testNestedObserverEvents = (tc: t.TestCase) => { + const { array0, users } = init(tc, { users: 2 }); + assert(array0); + const vals: number[] = []; + array0.observe((e) => { + if (array0.length === 1) { + // inserting, will call this observer again + // we expect that this observer is called after this event handler finishedn + array0.insert(1, [1]); + vals.push(0); + } else { + // this should be called the second time an element is inserted (above case) + vals.push(1); + } + }); + array0.insert(0, [0]); + deepEqual(vals, [0, 1]); + deepEqual(array0.toArray(), [0, 1]); + compare(users); + }; + + const testInsertAndDeleteEventsForTypes = (tc: t.TestCase) => { + const { array0, users } = init(tc, { users: 2 }); + assert(array0); + let event: Record | null = null; + array0.observe((e) => { + event = e; + }); + array0.insert(0, [new Y.Array()]); + assert(event !== null); + event = null; + array0.delete(0); + assert(event !== null); + event = null; + compare(users); + }; + + /** + * This issue has been reported in https://discuss.yjs.dev/t/order-in-which-events-yielded-by-observedeep-should-be-applied/261/2 + * + * Deep observers generate multiple events. When an array added at item at, say, position 0, + * and item 1 changed then the array-add event should fire first so that the change event + * path is correct. A array binding might lead to an inconsistent state otherwise. + */ + const testObserveDeepEventOrder = (tc: t.TestCase) => { + const { array0, users } = init(tc, { users: 2 }); + assert(array0); + let events: any[] = []; + array0.observeDeep((e) => { + events = e; + }); + array0.insert(0, [new Y.Map()]); + users[0].transact(() => { + array0.get(0).set("a", "a"); + array0.insert(0, [0]); + }); + for (let i = 1; i < events.length; i++) { + assert( + events[i - 1].path.length <= events[i].path.length, + "path size increases, fire top-level events first", + ); + } + }; + + /** + * Correct index when computing event.path in observeDeep - https://github.com/yjs/yjs/issues/457 + */ + const testObservedeepIndexes = () => { + const doc = new Y.Doc(); + const map = doc.createMap(); + // 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()]); + let eventPath: any[] = []; + 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); + deepEqual(eventPath, ["my-array", 3]); + }; + + const testChangeEvent = (tc: t.TestCase) => { + const { array0, users } = init(tc, { users: 2 }); + assert(array0); + let changes: any = null; + array0.observe((e) => { + changes = e.changes; + }); + const newArr = new Y.Array(); + array0.insert(0, [newArr, 4, "dtrn"]); + assert( + changes !== null && + changes.added.size === 2 && + changes.deleted.size === 0, ); - } -}; - -/** - * Correct index when computing event.path in observeDeep - https://github.com/yjs/yjs/issues/457 - */ -export const testObservedeepIndexes = (_tc: t.TestCase) => { - const doc = new Y.Doc(); - const map = doc.createMap(); - // 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()]); - let eventPath: any[] = []; - 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]); -}; - -export const testChangeEvent = (tc: t.TestCase) => { - const { array0, users } = init(tc, { users: 2 }); - t.assert(array0); - let changes: any = null; - array0.observe((e) => { - changes = e.changes; - }); - 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; - array0.delete(0, 2); - t.assert( - changes !== null && changes.added.size === 0 && changes.deleted.size === 2, - ); - t.compare(changes.delta, [{ delete: 2 }]); - changes = null; - 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] }]); - compare(users); -}; - -export const testInsertAndDeleteEventsForTypes2 = (tc: t.TestCase) => { - const { array0, users } = init(tc, { users: 2 }); - t.assert(array0); - const events: Record[] = []; - array0.observe((e) => { - events.push(e); - }); - array0.insert(0, ["hi", new Y.Map()]); - t.assert( - events.length === 1, - "Event is triggered exactly once for insertion of two elements", - ); - array0.delete(1); - t.assert(events.length === 2, "Event is triggered exactly once for deletion"); - compare(users); -}; - -/** - * This issue has been reported here https://github.com/yjs/yjs/issues/155 - * @param {t.TestCase} tc - */ -export const testNewChildDoesNotEmitEventInTransaction = (tc: t.TestCase) => { - const { array0, users } = init(tc, { users: 2 }); - t.assert(array0); - let fired = false; - users[0].transact(() => { - const newMap = new Y.Map(); - newMap.observe(() => { - fired = true; + deepEqual(changes.delta, [{ insert: [newArr, 4, "dtrn"] }]); + changes = null; + array0.delete(0, 2); + assert( + changes !== null && + changes.added.size === 0 && + changes.deleted.size === 2, + ); + deepEqual(changes.delta, [{ delete: 2 }]); + changes = null; + array0.insert(1, [0.1]); + assert( + changes !== null && + changes.added.size === 1 && + changes.deleted.size === 0, + ); + deepEqual(changes.delta, [{ retain: 1 }, { insert: [0.1] }]); + compare(users); + }; + + const testInsertAndDeleteEventsForTypes2 = (tc: t.TestCase) => { + const { array0, users } = init(tc, { users: 2 }); + assert(array0); + const events: Record[] = []; + array0.observe((e) => { + events.push(e); }); - array0.insert(0, [newMap]); - newMap.set("tst", 42); - }); - t.assert(!fired, "Event does not trigger"); -}; - -export const testGarbageCollector = (tc: t.TestCase) => { - const { testConnector, users, array0 } = init(tc, { users: 3 }); - t.assert(array0); - array0.insert(0, ["x", "y", "z"]); - testConnector.flushAllMessages(); - users[0].disconnect(); - array0.delete(0, 3); - users[0].connect(); - testConnector.flushAllMessages(); - compare(users); -}; - -/** - * @param {t.TestCase} tc - */ -export const testEventTargetIsSetCorrectlyOnLocal = (tc: t.TestCase) => { - const { array0, users } = init(tc, { users: 3 }); - t.assert(array0); - let event: any; - array0.observe((e) => { - event = e; - }); - array0.insert(0, ["stuff"]); - t.assert(event.target === array0, '"target" property is set correctly'); - compare(users); -}; - -export const testEventTargetIsSetCorrectlyOnRemote = (tc: t.TestCase) => { - const { testConnector, array0, array1, users } = init(tc, { users: 3 }); - t.assert(array0); - t.assert(array1); - let event: any; - array0.observe((e) => { - event = e; - }); - array1.insert(0, ["stuff"]); - testConnector.flushAllMessages(); - t.assert(event.target === array0, '"target" property is set correctly'); - compare(users); -}; - -export const testIteratingArrayContainingTypes = (tc: t.TestCase) => { - const y = new Y.Doc(); - const arr = y.getOrCreateArray("arr"); - const numItems = 10; - for (let i = 0; i < numItems; i++) { - const map = new Y.Map(); - map.set("value", i); - arr.push([map]); - } - let cnt = 0; - for (const item of arr.iter()) { - t.assert(item.get("value") === cnt++, "value is correct"); - } - y.destroy(); -}; - -let _uniqueNumber = 0; -const getUniqueNumber = () => _uniqueNumber++; - -const arrayTransactions: Array< - (arg0: Y.Doc, arg1: prng.PRNG, arg2: any) => void -> = [ - function insert(user: Y.Doc, gen: prng.PRNG) { - const yarray = user.getOrCreateArray("array"); - const uniqueNumber = getUniqueNumber(); - const content: number[] = []; - const len = prng.int32(gen, 1, 4); - for (let i = 0; i < len; i++) { - content.push(uniqueNumber); + array0.insert(0, ["hi", new Y.Map()]); + equal( + events.length, + 1, + "Event is triggered exactly once for insertion of two elements", + ); + array0.delete(1); + equal(events.length, 2, "Event is triggered exactly once for deletion"); + compare(users); + }; + + /** + * This issue has been reported here https://github.com/yjs/yjs/issues/155 + * @param {t.TestCase} tc + */ + const testNewChildDoesNotEmitEventInTransaction = (tc: t.TestCase) => { + const { array0, users } = init(tc, { users: 2 }); + assert(array0); + let fired = false; + users[0].transact(() => { + const newMap = new Y.Map(); + newMap.observe(() => { + fired = true; + }); + array0.insert(0, [newMap]); + newMap.set("tst", 42); + }); + assert(!fired, "Event does not trigger"); + }; + + const testGarbageCollector = (tc: t.TestCase) => { + const { testConnector, users, array0 } = init(tc, { users: 3 }); + assert(array0); + array0.insert(0, ["x", "y", "z"]); + testConnector.flushAllMessages(); + users[0].disconnect(); + array0.delete(0, 3); + users[0].connect(); + testConnector.flushAllMessages(); + compare(users); + }; + + /** + * @param {t.TestCase} tc + */ + const testEventTargetIsSetCorrectlyOnLocal = (tc: t.TestCase) => { + const { array0, users } = init(tc, { users: 3 }); + assert(array0); + let event: any; + array0.observe((e) => { + event = e; + }); + array0.insert(0, ["stuff"]); + assert(event.target === array0, '"target" property is set correctly'); + compare(users); + }; + + const testEventTargetIsSetCorrectlyOnRemote = (tc: t.TestCase) => { + const { testConnector, array0, array1, users } = init(tc, { users: 3 }); + assert(array0); + assert(array1); + let event: any; + array0.observe((e) => { + event = e; + }); + array1.insert(0, ["stuff"]); + testConnector.flushAllMessages(); + assert(event.target === array0, '"target" property is set correctly'); + compare(users); + }; + + const testIteratingArrayContainingTypes = () => { + const y = new Y.Doc(); + const arr = y.getOrCreateArray("arr"); + const numItems = 10; + for (let i = 0; i < numItems; i++) { + const map = new Y.Map(); + map.set("value", i); + arr.push([map]); } - const pos = prng.int32(gen, 0, yarray.length); - const oldContent = yarray.toArray(); - yarray.insert(pos, content); - oldContent.splice(pos, 0, ...content); - t.compareArrays(yarray.toArray(), oldContent); // we want to make sure that fastSearch markers insert at the correct position - }, - function insertTypeArray(user: Y.Doc, gen: prng.PRNG) { - const yarray = user.getOrCreateArray("array"); - const pos = prng.int32(gen, 0, yarray.length); - yarray.insert(pos, [new Y.Array()]); - const array2 = yarray.get(pos); - array2.insert(0, [1, 2, 3, 4]); - }, - function insertTypeMap(user: Y.Doc, gen: prng.PRNG) { - const yarray = user.getOrCreateArray("array"); - const pos = prng.int32(gen, 0, yarray.length); - yarray.insert(pos, [new Y.Map()]); - const map = yarray.get(pos); - map.set("someprop", 42); - map.set("someprop", 43); - map.set("someprop", 44); - }, - function insertTypeNull(user: Y.Doc, gen: prng.PRNG) { - const yarray = user.getOrCreateArray("array"); - const pos = prng.int32(gen, 0, yarray.length); - yarray.insert(pos, [null]); - }, - function _delete(user: Y.Doc, gen: prng.PRNG) { - const yarray = user.getOrCreateArray("array"); - const length = yarray.length; - if (length > 0) { - let somePos = prng.int32(gen, 0, length - 1); - let delLength = prng.int32(gen, 1, math.min(2, length - somePos)); - if (prng.bool(gen)) { - const type = yarray.get(somePos); - if (type instanceof Y.Array && type.length > 0) { - somePos = prng.int32(gen, 0, type.length - 1); - delLength = prng.int32(gen, 0, math.min(2, type.length - somePos)); - type.delete(somePos, delLength); + let cnt = 0; + for (const item of arr.iter()) { + assert(item.get("value") === cnt++, "value is correct"); + } + y.destroy(); + }; + + let _uniqueNumber = 0; + const getUniqueNumber = () => _uniqueNumber++; + + const arrayTransactions: Array< + (arg0: Y.Doc, arg1: prng.PRNG, arg2: any) => void + > = [ + function insert(user: Y.Doc, gen: prng.PRNG) { + const yarray = user.getOrCreateArray("array"); + const uniqueNumber = getUniqueNumber(); + const content: number[] = []; + const len = prng.int32(gen, 1, 4); + for (let i = 0; i < len; i++) { + content.push(uniqueNumber); + } + const pos = prng.int32(gen, 0, yarray.length); + const oldContent = yarray.toArray(); + yarray.insert(pos, content); + oldContent.splice(pos, 0, ...content); + deepEqual(yarray.toArray(), oldContent); // we want to make sure that fastSearch markers insert at the correct position + }, + function insertTypeArray(user: Y.Doc, gen: prng.PRNG) { + const yarray = user.getOrCreateArray("array"); + const pos = prng.int32(gen, 0, yarray.length); + yarray.insert(pos, [new Y.Array()]); + const array2 = yarray.get(pos); + array2.insert(0, [1, 2, 3, 4]); + }, + function insertTypeMap(user: Y.Doc, gen: prng.PRNG) { + const yarray = user.getOrCreateArray("array"); + const pos = prng.int32(gen, 0, yarray.length); + yarray.insert(pos, [new Y.Map()]); + const map = yarray.get(pos); + map.set("someprop", 42); + map.set("someprop", 43); + map.set("someprop", 44); + }, + function insertTypeNull(user: Y.Doc, gen: prng.PRNG) { + const yarray = user.getOrCreateArray("array"); + const pos = prng.int32(gen, 0, yarray.length); + yarray.insert(pos, [null]); + }, + function _delete(user: Y.Doc, gen: prng.PRNG) { + const yarray = user.getOrCreateArray("array"); + const length = yarray.length; + if (length > 0) { + let somePos = prng.int32(gen, 0, length - 1); + let delLength = prng.int32(gen, 1, math.min(2, length - somePos)); + if (prng.bool(gen)) { + const type = yarray.get(somePos); + if (type instanceof Y.Array && type.length > 0) { + somePos = prng.int32(gen, 0, type.length - 1); + delLength = prng.int32(gen, 0, math.min(2, type.length - somePos)); + type.delete(somePos, delLength); + } + } else { + const oldContent = yarray.toArray(); + yarray.delete(somePos, delLength); + oldContent.splice(somePos, delLength); + deepEqual(yarray.toArray(), oldContent); } - } else { - const oldContent = yarray.toArray(); - yarray.delete(somePos, delLength); - oldContent.splice(somePos, delLength); - t.compareArrays(yarray.toArray(), oldContent); } - } - }, -]; - -/** - * @param {t.TestCase} tc - */ -export const testRepeatGeneratingYarrayTests6 = (tc: t.TestCase) => { - applyRandomTests(tc, arrayTransactions, 6); -}; - -export const testRepeatGeneratingYarrayTests40 = (tc: t.TestCase) => { - applyRandomTests(tc, arrayTransactions, 40); -}; - -export const testRepeatGeneratingYarrayTests42 = (tc: t.TestCase) => { - applyRandomTests(tc, arrayTransactions, 42); -}; - -export const testRepeatGeneratingYarrayTests43 = (tc: t.TestCase) => { - applyRandomTests(tc, arrayTransactions, 43); -}; - -export const testRepeatGeneratingYarrayTests44 = (tc: t.TestCase) => { - applyRandomTests(tc, arrayTransactions, 44); -}; - -export const testRepeatGeneratingYarrayTests45 = (tc: t.TestCase) => { - applyRandomTests(tc, arrayTransactions, 45); -}; - -export const testRepeatGeneratingYarrayTests46 = (tc: t.TestCase) => { - applyRandomTests(tc, arrayTransactions, 46); -}; - -export const testRepeatGeneratingYarrayTests300 = (tc: t.TestCase) => { - applyRandomTests(tc, arrayTransactions, 300); -}; - -export const testRepeatGeneratingYarrayTests400 = (tc: t.TestCase) => { - applyRandomTests(tc, arrayTransactions, 400); -}; - -export const testRepeatGeneratingYarrayTests500 = (tc: t.TestCase) => { - applyRandomTests(tc, arrayTransactions, 500); -}; - -export const testRepeatGeneratingYarrayTests600 = (tc: t.TestCase) => { - applyRandomTests(tc, arrayTransactions, 600); -}; - -export const testRepeatGeneratingYarrayTests1000 = (tc: t.TestCase) => { - applyRandomTests(tc, arrayTransactions, 1000); -}; - -export const testRepeatGeneratingYarrayTests1800 = (tc: t.TestCase) => { - applyRandomTests(tc, arrayTransactions, 1800); -}; - -export const testRepeatGeneratingYarrayTests3000 = (tc: t.TestCase) => { - t.skip(!t.production); - applyRandomTests(tc, arrayTransactions, 3000); -}; - -export const testRepeatGeneratingYarrayTests5000 = (tc: t.TestCase) => { - t.skip(!t.production); - applyRandomTests(tc, arrayTransactions, 5000); -}; - -export const testRepeatGeneratingYarrayTests30000 = (tc: t.TestCase) => { - t.skip(!t.production); - applyRandomTests(tc, arrayTransactions, 30000); -}; + }, + ]; + + /** + * @param {t.TestCase} tc + */ + const testRepeatGeneratingYarrayTests6 = (tc: t.TestCase) => { + applyRandomTests(tc, arrayTransactions, 6); + }; + + const testRepeatGeneratingYarrayTests40 = (tc: t.TestCase) => { + applyRandomTests(tc, arrayTransactions, 40); + }; + + const testRepeatGeneratingYarrayTests42 = (tc: t.TestCase) => { + applyRandomTests(tc, arrayTransactions, 42); + }; + + const testRepeatGeneratingYarrayTests43 = (tc: t.TestCase) => { + applyRandomTests(tc, arrayTransactions, 43); + }; + + const testRepeatGeneratingYarrayTests44 = (tc: t.TestCase) => { + applyRandomTests(tc, arrayTransactions, 44); + }; + + const testRepeatGeneratingYarrayTests45 = (tc: t.TestCase) => { + applyRandomTests(tc, arrayTransactions, 45); + }; + + const testRepeatGeneratingYarrayTests46 = (tc: t.TestCase) => { + applyRandomTests(tc, arrayTransactions, 46); + }; + + const testRepeatGeneratingYarrayTests300 = (tc: t.TestCase) => { + applyRandomTests(tc, arrayTransactions, 300); + }; + + const testRepeatGeneratingYarrayTests400 = (tc: t.TestCase) => { + applyRandomTests(tc, arrayTransactions, 400); + }; + + const testRepeatGeneratingYarrayTests500 = (tc: t.TestCase) => { + applyRandomTests(tc, arrayTransactions, 500); + }; + + const testRepeatGeneratingYarrayTests600 = (tc: t.TestCase) => { + applyRandomTests(tc, arrayTransactions, 600); + }; + + const testRepeatGeneratingYarrayTests1000 = (tc: t.TestCase) => { + applyRandomTests(tc, arrayTransactions, 1000); + }; + + const testRepeatGeneratingYarrayTests1800 = (tc: t.TestCase) => { + applyRandomTests(tc, arrayTransactions, 1800); + }; + + const testRepeatGeneratingYarrayTests3000 = (tc: t.TestCase) => { + t.skip(!t.production); + applyRandomTests(tc, arrayTransactions, 3000); + }; + + const testRepeatGeneratingYarrayTests5000 = (tc: t.TestCase) => { + t.skip(!t.production); + applyRandomTests(tc, arrayTransactions, 5000); + }; + + const testRepeatGeneratingYarrayTests30000 = (tc: t.TestCase) => { + t.skip(!t.production); + applyRandomTests(tc, arrayTransactions, 30000); + }; +}); diff --git a/y-octo-node/tsconfig.json b/y-octo-node/tsconfig.json index dd11afc..edc64f5 100644 --- a/y-octo-node/tsconfig.json +++ b/y-octo-node/tsconfig.json @@ -5,7 +5,7 @@ "outDir": "lib", "composite": true }, - "include": ["index.d.ts", "tests/**/*.mts"], + "include": ["index.d.ts", "tests/**/*.(mts|ts)"], "ts-node": { "esm": true, "experimentalSpecifierResolution": "node" diff --git a/yarn.lock b/yarn.lock index 9ffe11a..304786e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -326,6 +326,7 @@ __metadata: prompts: ^2.4.2 tsx: ^4.16.2 typescript: ^5.1.6 + y-protocols: ^1.0.6 yjs: ^13.6.18 languageName: unknown linkType: soft @@ -1236,7 +1237,7 @@ __metadata: languageName: node linkType: hard -"lib0@npm:^0.2.86": +"lib0@npm:^0.2.85, lib0@npm:^0.2.86": version: 0.2.94 resolution: "lib0@npm:0.2.94" dependencies: @@ -2347,6 +2348,17 @@ __metadata: languageName: node linkType: hard +"y-protocols@npm:^1.0.6": + version: 1.0.6 + resolution: "y-protocols@npm:1.0.6" + dependencies: + lib0: ^0.2.85 + peerDependencies: + yjs: ^13.0.0 + checksum: 4b57c8811befcf2e45c3d47830005f8a33e626c734f78a42fe8a4fa3caad2233ba85a7c8bceefbd52ffc40130d3f3faee664fd0d1c324ff1fa8817a056ccdc1c + languageName: node + linkType: hard + "y18n@npm:^5.0.5": version: 5.0.8 resolution: "y18n@npm:5.0.8" From 7c861a2151c162e00d4143b05e7dc9a59290f0e5 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Wed, 3 Jul 2024 17:04:25 +0800 Subject: [PATCH 11/75] fix: either type cannot match after rename --- y-octo-node/index.d.ts | 13 +++++-------- y-octo-node/index.js | 9 +++++---- y-octo-node/package.json | 4 ++-- y-octo-node/src/map.rs | 2 +- y-octo-node/src/text.rs | 2 +- y-octo-node/tests/array.spec.mts | 8 ++++---- y-octo-node/tests/doc.spec.mts | 2 +- y-octo-node/tests/map.spec.mts | 14 +++++++------- y-octo-node/tests/text.spec.mts | 6 +++--- y-octo-node/yocto.d.ts | 8 ++++++++ y-octo-node/yocto.js | 8 ++++++++ 11 files changed, 45 insertions(+), 31 deletions(-) create mode 100644 y-octo-node/yocto.d.ts create mode 100644 y-octo-node/yocto.js diff --git a/y-octo-node/index.d.ts b/y-octo-node/index.d.ts index fb00ea5..f6f51bc 100644 --- a/y-octo-node/index.d.ts +++ b/y-octo-node/index.d.ts @@ -12,8 +12,7 @@ export declare function encodeSnapshot(snapshot: YSnapshot): Buffer export declare function applyUpdate(doc: Doc, update: Buffer): void export declare function mergeUpdates(updates: Array): Buffer export declare function isAbstractType(unknown: unknown): boolean -export type YArray = Array -export class Array { +export class YArray { constructor() get length(): number get isEmpty(): boolean @@ -40,10 +39,10 @@ export class Doc { get guid(): string get store(): YStore get keys(): Array - getOrCreateArray(key: string): Array + getOrCreateArray(key: string): YArray getOrCreateText(key: string): YText getOrCreateMap(key: string): YMap - createArray(): Array + createArray(): YArray createText(): YText createMap(): YMap applyUpdate(update: Buffer): void @@ -52,8 +51,7 @@ export class Doc { onUpdate(callback: (result: Uint8Array) => void): void transact(callback: (...args: any[]) => any): void } -export type YMap = Map -export class Map { +export class YMap { constructor() get length(): number get isEmpty(): boolean @@ -64,8 +62,7 @@ export class Map { observe(callback: (...args: any[]) => any): void observeDeep(callback: (...args: any[]) => any): void } -export type YText = Text -export class Text { +export class YText { constructor() get len(): number get isEmpty(): boolean diff --git a/y-octo-node/index.js b/y-octo-node/index.js index 190c7da..ea5fb9b 100644 --- a/y-octo-node/index.js +++ b/y-octo-node/index.js @@ -252,9 +252,10 @@ if (!nativeBinding) { throw new Error(`Failed to load native binding`) } -const { Array, Doc, encodeStateAsUpdate, encodeStateVector, createDeleteSetFromStructStore, equalDeleteSets, snapshot, encodeSnapshot, applyUpdate, mergeUpdates, isAbstractType, Map, Text, Store, DeleteSet, YSnapshot } = nativeBinding +const { YArray, YArrayIterator, Doc, encodeStateAsUpdate, encodeStateVector, createDeleteSetFromStructStore, equalDeleteSets, snapshot, encodeSnapshot, applyUpdate, mergeUpdates, isAbstractType, YMap, YText, Store, DeleteSet, YSnapshot } = nativeBinding -module.exports.Array = Array +module.exports.YArray = YArray +module.exports.YArrayIterator = YArrayIterator module.exports.Doc = Doc module.exports.encodeStateAsUpdate = encodeStateAsUpdate module.exports.encodeStateVector = encodeStateVector @@ -265,8 +266,8 @@ module.exports.encodeSnapshot = encodeSnapshot module.exports.applyUpdate = applyUpdate module.exports.mergeUpdates = mergeUpdates module.exports.isAbstractType = isAbstractType -module.exports.Map = Map -module.exports.Text = Text +module.exports.YMap = YMap +module.exports.YText = YText module.exports.Store = Store module.exports.DeleteSet = DeleteSet module.exports.YSnapshot = YSnapshot diff --git a/y-octo-node/package.json b/y-octo-node/package.json index bbb8a9f..719c9cd 100644 --- a/y-octo-node/package.json +++ b/y-octo-node/package.json @@ -1,8 +1,8 @@ { "name": "@y-octo/node", "private": true, - "main": "index.js", - "types": "index.d.ts", + "main": "yocto.js", + "types": "yocto.d.ts", "napi": { "name": "y-octo", "triples": { diff --git a/y-octo-node/src/map.rs b/y-octo-node/src/map.rs index 6670f41..f18ecf8 100644 --- a/y-octo-node/src/map.rs +++ b/y-octo-node/src/map.rs @@ -3,7 +3,7 @@ use y_octo::{Any, Map, Value}; use super::*; -#[napi(js_name = "Map")] +#[napi] pub struct YMap { pub(crate) map: Map, } diff --git a/y-octo-node/src/text.rs b/y-octo-node/src/text.rs index 2271e00..ca67c49 100644 --- a/y-octo-node/src/text.rs +++ b/y-octo-node/src/text.rs @@ -2,7 +2,7 @@ use y_octo::Text; use super::*; -#[napi(js_name = "Text")] +#[napi] pub struct YText { pub(crate) text: Text, } diff --git a/y-octo-node/tests/array.spec.mts b/y-octo-node/tests/array.spec.mts index d236ce2..cc7bf62 100644 --- a/y-octo-node/tests/array.spec.mts +++ b/y-octo-node/tests/array.spec.mts @@ -1,14 +1,14 @@ import assert, { equal, deepEqual } from "node:assert"; import { test } from "node:test"; -import { Doc, Array } from "../index"; +import * as YOcto from "../yocto"; test("array test", { concurrency: false }, async (t) => { let client_id: number; - let doc: Doc; + let doc: YOcto.Doc; t.beforeEach(async () => { client_id = (Math.random() * 100000) | 0; - doc = new Doc(client_id); + doc = new YOcto.Doc(client_id); }); t.afterEach(async () => { @@ -56,7 +56,7 @@ test("array test", { concurrency: false }, async (t) => { sub.insert(3, "hello world"); equal(sub.length, 4); - let sub2 = map.get("sub"); + let sub2 = map.get("sub"); assert(sub2); equal(sub2.get(0), true); equal(sub2.get(1), false); diff --git a/y-octo-node/tests/doc.spec.mts b/y-octo-node/tests/doc.spec.mts index 1cde13d..380ec5d 100644 --- a/y-octo-node/tests/doc.spec.mts +++ b/y-octo-node/tests/doc.spec.mts @@ -1,7 +1,7 @@ import assert, { equal } from "node:assert"; import { test } from "node:test"; -import * as YOcto from "../index"; +import * as YOcto from "../yocto"; import * as Y from "yjs"; test("doc test", { concurrency: false }, async (t) => { diff --git a/y-octo-node/tests/map.spec.mts b/y-octo-node/tests/map.spec.mts index e8f1ae9..c817294 100644 --- a/y-octo-node/tests/map.spec.mts +++ b/y-octo-node/tests/map.spec.mts @@ -2,14 +2,14 @@ import assert, { equal, deepEqual } from "node:assert"; import { test } from "node:test"; import * as Y from "yjs"; -import { Doc, Array, Map, Text } from "../index"; +import * as YOcto from "../yocto"; test("map test", { concurrency: false }, async (t) => { let client_id: number; - let doc: Doc; + let doc: YOcto.Doc; t.beforeEach(async () => { client_id = (Math.random() * 100000) | 0; - doc = new Doc(client_id); + doc = new YOcto.Doc(client_id); }); t.afterEach(async () => { @@ -52,7 +52,7 @@ test("map test", { concurrency: false }, async (t) => { sub.set("d", "hello world"); equal(sub.length, 4); - let sub2 = map.get("sub"); + let sub2 = map.get("sub"); assert(sub2); equal(sub2.get("a"), true); equal(sub2.get("b"), false); @@ -131,9 +131,9 @@ test("map test", { concurrency: false }, async (t) => { doc.applyUpdate(Buffer.from(Y.encodeStateAsUpdate(doc2))); let map = doc.getOrCreateMap("map"); - let sub_array = map.get("array"); - let sub_map = map.get("map"); - let sub_text = map.get("text"); + let sub_array = map.get("array"); + let sub_map = map.get("map"); + let sub_text = map.get("text"); assert(sub_array); equal(sub_array.length, 4); diff --git a/y-octo-node/tests/text.spec.mts b/y-octo-node/tests/text.spec.mts index f04968e..ebcf588 100644 --- a/y-octo-node/tests/text.spec.mts +++ b/y-octo-node/tests/text.spec.mts @@ -1,14 +1,14 @@ import assert, { equal, deepEqual } from "node:assert"; import { test } from "node:test"; -import { Doc, Text } from "../index"; +import * as YOcto from "../yocto"; test("text test", { concurrency: false }, async (t) => { let client_id: number; - let doc: Doc; + let doc: YOcto.Doc; t.beforeEach(async () => { client_id = (Math.random() * 100000) | 0; - doc = new Doc(client_id); + doc = new YOcto.Doc(client_id); }); t.afterEach(async () => { diff --git a/y-octo-node/yocto.d.ts b/y-octo-node/yocto.d.ts new file mode 100644 index 0000000..02f67a4 --- /dev/null +++ b/y-octo-node/yocto.d.ts @@ -0,0 +1,8 @@ +import * as Y from "./index"; + +export class Doc extends Y.Doc {} +export class Array extends Y.YArray {} +export class Map extends Y.YMap {} +export class Text extends Y.YText {} + +export * from "./index"; diff --git a/y-octo-node/yocto.js b/y-octo-node/yocto.js new file mode 100644 index 0000000..f6dc11c --- /dev/null +++ b/y-octo-node/yocto.js @@ -0,0 +1,8 @@ +import * as Y from "./index"; + +export const Doc = Y.Doc; +export const Array = Y.YArray; +export const Map = Y.YMap; +export const Text = Y.YText; + +export * from "./index"; From ac3cff4c3529260a8fdd6f249688043d2d3c499b Mon Sep 17 00:00:00 2001 From: DarkSky Date: Thu, 4 Jul 2024 11:03:41 +0800 Subject: [PATCH 12/75] chore: match client id behaver --- y-octo-node/index.d.ts | 1 + y-octo-node/src/doc.rs | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/y-octo-node/index.d.ts b/y-octo-node/index.d.ts index f6f51bc..aa909f8 100644 --- a/y-octo-node/index.d.ts +++ b/y-octo-node/index.d.ts @@ -36,6 +36,7 @@ export type YDoc = Doc export class Doc { constructor(clientId?: number | undefined | null) get clientId(): number + set clientId(clientId: number) get guid(): string get store(): YStore get keys(): Array diff --git a/y-octo-node/src/doc.rs b/y-octo-node/src/doc.rs index 74b0bd8..dafb101 100644 --- a/y-octo-node/src/doc.rs +++ b/y-octo-node/src/doc.rs @@ -29,6 +29,11 @@ impl YDoc { self.doc.client() as i64 } + #[napi(setter)] + pub fn set_client_id(&mut self, client_id: i64) { + self.doc.set_client(client_id as u64); + } + #[napi(getter)] pub fn guid(&self) -> &str { self.doc.guid() From 471fcfdf55c6b4673634e0c2a48608bef60d78e3 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Thu, 4 Jul 2024 15:48:16 +0800 Subject: [PATCH 13/75] feat: deep compare for doc --- y-octo-node/src/function.rs | 5 + y-octo-node/tests/array.spec.mts | 98 ----- y-octo-node/tests/array.spec.ts | 95 +++++ y-octo-node/tests/doc.spec.mts | 99 ----- y-octo-node/tests/doc.spec.ts | 96 +++++ y-octo-node/tests/map.spec.mts | 152 ------- y-octo-node/tests/map.spec.ts | 149 +++++++ y-octo-node/tests/text.spec.mts | 54 --- y-octo-node/tests/text.spec.ts | 51 +++ y-octo-node/tests/yjs/testHelper.ts | 13 +- y-octo-node/tests/yjs/yarray.spec.mts | 587 -------------------------- y-octo-node/tests/yjs/yarray.spec.ts | 579 +++++++++++++++++++++++++ y-octo/src/doc/codec/item.rs | 16 + y-octo/src/doc/document.rs | 7 + y-octo/src/doc/store.rs | 33 ++ 15 files changed, 1037 insertions(+), 997 deletions(-) delete mode 100644 y-octo-node/tests/array.spec.mts create mode 100644 y-octo-node/tests/array.spec.ts delete mode 100644 y-octo-node/tests/doc.spec.mts create mode 100644 y-octo-node/tests/doc.spec.ts delete mode 100644 y-octo-node/tests/map.spec.mts create mode 100644 y-octo-node/tests/map.spec.ts delete mode 100644 y-octo-node/tests/text.spec.mts create mode 100644 y-octo-node/tests/text.spec.ts delete mode 100644 y-octo-node/tests/yjs/yarray.spec.mts create mode 100644 y-octo-node/tests/yjs/yarray.spec.ts diff --git a/y-octo-node/src/function.rs b/y-octo-node/src/function.rs index b1d2c6c..2ca35ce 100644 --- a/y-octo-node/src/function.rs +++ b/y-octo-node/src/function.rs @@ -18,6 +18,11 @@ pub fn encode_state_vector(doc: &YDoc) -> Result { Ok(encoder.into_inner().into()) } +#[napi] +pub fn compare_struct_stores(store: &YStore, other: &YStore) -> bool { + store.doc.store_compare(&other.doc) +} + // delete set #[napi] diff --git a/y-octo-node/tests/array.spec.mts b/y-octo-node/tests/array.spec.mts deleted file mode 100644 index cc7bf62..0000000 --- a/y-octo-node/tests/array.spec.mts +++ /dev/null @@ -1,98 +0,0 @@ -import assert, { equal, deepEqual } from "node:assert"; -import { test } from "node:test"; - -import * as YOcto from "../yocto"; - -test("array test", { concurrency: false }, async (t) => { - let client_id: number; - let doc: YOcto.Doc; - t.beforeEach(async () => { - client_id = (Math.random() * 100000) | 0; - doc = new YOcto.Doc(client_id); - }); - - t.afterEach(async () => { - client_id = -1; - // @ts-ignore - doc must not null in next range - doc = null; - }); - - await t.test("array should be created", () => { - let arr = doc.getOrCreateArray("arr"); - deepEqual(doc.keys, ["arr"]); - equal(arr.length, 0); - }); - - await t.test("array editing", () => { - let arr = doc.getOrCreateArray("arr"); - arr.insert(0, true); - arr.insert(1, false); - arr.insert(2, 1); - arr.insert(3, "hello world"); - equal(arr.length, 4); - equal(arr.get(0), true); - equal(arr.get(1), false); - equal(arr.get(2), 1); - equal(arr.get(3), "hello world"); - equal(arr.length, 4); - arr.delete(1, 1); - equal(arr.length, 3); - equal(arr.get(2), "hello world"); - deepEqual(arr.slice(1, 3), [1, "hello world"]); - deepEqual( - arr.map((v) => v), - [true, 1, "hello world"], - ); - }); - - await t.test("sub array should can edit", () => { - let map = doc.getOrCreateMap("map"); - let sub = doc.createArray(); - map.set("sub", sub); - - sub.insert(0, true); - sub.insert(1, false); - sub.insert(2, 1); - sub.insert(3, "hello world"); - equal(sub.length, 4); - - let sub2 = map.get("sub"); - assert(sub2); - equal(sub2.get(0), true); - equal(sub2.get(1), false); - equal(sub2.get(2), 1); - equal(sub2.get(3), "hello world"); - equal(sub2.length, 4); - deepEqual(sub2.slice(1, 3), [false, 1]); - deepEqual( - sub2.map((v) => v), - [true, false, 1, "hello world"], - ); - }); - - await t.test("array should support iterator", () => { - let arr = doc.getOrCreateArray("arr"); - arr.insert(0, true); - arr.insert(1, false); - arr.insert(2, 1); - arr.insert(3, "hello world"); - let i = 0; - for (let v of arr.iter()) { - switch (i) { - case 0: - equal(v, true); - break; - case 1: - equal(v, false); - break; - case 2: - equal(v, 1); - break; - case 3: - equal(v, "hello world"); - break; - } - i++; - } - }); -}); diff --git a/y-octo-node/tests/array.spec.ts b/y-octo-node/tests/array.spec.ts new file mode 100644 index 0000000..a9d15ab --- /dev/null +++ b/y-octo-node/tests/array.spec.ts @@ -0,0 +1,95 @@ +import test from "ava"; + +import * as YOcto from "../yocto"; + +let client_id: number; +let doc: YOcto.Doc; +test.beforeEach(async () => { + client_id = (Math.random() * 100000) | 0; + doc = new YOcto.Doc(client_id); +}); + +test.afterEach(async () => { + client_id = -1; + // @ts-ignore - doc must not null in next range + doc = null; +}); + +test("array should be created", (t) => { + let arr = doc.getOrCreateArray("arr"); + t.deepEqual(doc.keys, ["arr"]); + t.is(arr.length, 0); +}); + +test("array editing", (t) => { + let arr = doc.getOrCreateArray("arr"); + arr.insert(0, true); + arr.insert(1, false); + arr.insert(2, 1); + arr.insert(3, "hello world"); + t.is(arr.length, 4); + t.is(arr.get(0), true); + t.is(arr.get(1), false); + t.is(arr.get(2), 1); + t.is(arr.get(3), "hello world"); + t.is(arr.length, 4); + arr.delete(1, 1); + t.is(arr.length, 3); + t.is(arr.get(2), "hello world"); + t.deepEqual(arr.slice(1, 3), [1, "hello world"]); + t.deepEqual( + arr.map((v) => v), + [true, 1, "hello world"], + ); +}); + +test("sub array should can edit", (t) => { + let map = doc.getOrCreateMap("map"); + let sub = doc.createArray(); + map.set("sub", sub); + + sub.insert(0, true); + sub.insert(1, false); + sub.insert(2, 1); + sub.insert(3, "hello world"); + t.is(sub.length, 4); + + let sub2 = map.get("sub"); + t.assert(sub2); + t.is(sub2.get(0), true); + t.is(sub2.get(1), false); + t.is(sub2.get(2), 1); + t.is(sub2.get(3), "hello world"); + t.is(sub2.length, 4); + t.deepEqual(sub2.slice(1, 3), [false, 1]); + t.deepEqual( + sub2.map((v) => v), + [true, false, 1, "hello world"], + ); +}); + +test("array should support iterator", (t) => { + let arr = doc.getOrCreateArray("arr"); + arr.insert(0, true); + arr.insert(1, false); + arr.insert(2, 1); + arr.insert(3, "hello world"); + let i = 0; + for (let v of arr.iter()) { + switch (i) { + case 0: + t.is(v, true); + break; + case 1: + t.is(v, false); + break; + case 2: + t.is(v, 1); + break; + case 3: + t.is(v, "hello world"); + break; + } + i++; + } +}); diff --git a/y-octo-node/tests/doc.spec.mts b/y-octo-node/tests/doc.spec.mts deleted file mode 100644 index 380ec5d..0000000 --- a/y-octo-node/tests/doc.spec.mts +++ /dev/null @@ -1,99 +0,0 @@ -import assert, { equal } from "node:assert"; -import { test } from "node:test"; - -import * as YOcto from "../yocto"; -import * as Y from "yjs"; - -test("doc test", { concurrency: false }, async (t) => { - let client_id: number; - let doc: YOcto.Doc; - t.beforeEach(async () => { - client_id = (Math.random() * 100000) | 0; - doc = new YOcto.Doc(client_id); - }); - - t.afterEach(async () => { - client_id = -1; - // @ts-ignore - doc must not null in next range - doc = null; - }); - - await t.test("doc id should be set", () => { - equal(doc.clientId, client_id); - }); - - await t.test("y-octo doc update should be apply", () => { - let array = doc.getOrCreateArray("array"); - let map = doc.getOrCreateMap("map"); - let text = doc.getOrCreateText("text"); - - array.insert(0, true); - array.insert(1, false); - array.insert(2, 1); - array.insert(3, "hello world"); - map.set("a", true); - map.set("b", false); - map.set("c", 1); - map.set("d", "hello world"); - text.insert(0, "a"); - text.insert(1, "b"); - text.insert(2, "c"); - - let doc2 = new YOcto.Doc(client_id); - doc2.applyUpdate(doc.encodeStateAsUpdateV1()); - - let array2 = doc2.getOrCreateArray("array"); - let map2 = doc2.getOrCreateMap("map"); - let text2 = doc2.getOrCreateText("text"); - - equal(doc2.clientId, client_id); - equal(array2.length, 4); - equal(array2.get(0), true); - equal(array2.get(1), false); - equal(array2.get(2), 1); - equal(array2.get(3), "hello world"); - equal(map2.length, 4); - equal(map2.get("a"), true); - equal(map2.get("b"), false); - equal(map2.get("c"), 1); - equal(map2.get("d"), "hello world"); - equal(text2.toString(), "abc"); - }); - - await t.test("yjs doc update should be apply", () => { - let doc2 = new Y.Doc(); - let array2 = doc2.getArray("array"); - let map2 = doc2.getMap("map"); - let text2 = doc2.getText("text"); - - array2.insert(0, [true]); - array2.insert(1, [false]); - array2.insert(2, [1]); - array2.insert(3, ["hello world"]); - map2.set("a", true); - map2.set("b", false); - map2.set("c", 1); - map2.set("d", "hello world"); - text2.insert(0, "a"); - text2.insert(1, "b"); - text2.insert(2, "c"); - - doc.applyUpdate(Buffer.from(Y.encodeStateAsUpdate(doc2))); - - let array = doc.getOrCreateArray("array"); - let map = doc.getOrCreateMap("map"); - let text = doc.getOrCreateText("text"); - - equal(array.length, 4); - equal(array.get(0), true); - equal(array.get(1), false); - equal(array.get(2), 1); - equal(array.get(3), "hello world"); - equal(map.length, 4); - equal(map.get("a"), true); - equal(map.get("b"), false); - equal(map.get("c"), 1); - equal(map.get("d"), "hello world"); - equal(text.toString(), "abc"); - }); -}); diff --git a/y-octo-node/tests/doc.spec.ts b/y-octo-node/tests/doc.spec.ts new file mode 100644 index 0000000..3a3c844 --- /dev/null +++ b/y-octo-node/tests/doc.spec.ts @@ -0,0 +1,96 @@ +import test from "ava"; + +import * as YOcto from "../yocto"; +import * as Y from "yjs"; + +let client_id: number; +let doc: YOcto.Doc; +test.beforeEach(async () => { + client_id = (Math.random() * 100000) | 0; + doc = new YOcto.Doc(client_id); +}); + +test.afterEach(async () => { + client_id = -1; + // @ts-ignore - doc must not null in next range + doc = null; +}); + +test("doc id should be set", (t) => { + t.is(doc.clientId, client_id); +}); + +test("y-octo doc update should be apply", (t) => { + let array = doc.getOrCreateArray("array"); + let map = doc.getOrCreateMap("map"); + let text = doc.getOrCreateText("text"); + + array.insert(0, true); + array.insert(1, false); + array.insert(2, 1); + array.insert(3, "hello world"); + map.set("a", true); + map.set("b", false); + map.set("c", 1); + map.set("d", "hello world"); + text.insert(0, "a"); + text.insert(1, "b"); + text.insert(2, "c"); + + let doc2 = new YOcto.Doc(client_id); + doc2.applyUpdate(doc.encodeStateAsUpdateV1()); + + let array2 = doc2.getOrCreateArray("array"); + let map2 = doc2.getOrCreateMap("map"); + let text2 = doc2.getOrCreateText("text"); + + t.is(doc2.clientId, client_id); + t.is(array2.length, 4); + t.is(array2.get(0), true); + t.is(array2.get(1), false); + t.is(array2.get(2), 1); + t.is(array2.get(3), "hello world"); + t.is(map2.length, 4); + t.is(map2.get("a"), true); + t.is(map2.get("b"), false); + t.is(map2.get("c"), 1); + t.is(map2.get("d"), "hello world"); + t.is(text2.toString(), "abc"); +}); + +test("yjs doc update should be apply", (t) => { + let doc2 = new Y.Doc(); + let array2 = doc2.getArray("array"); + let map2 = doc2.getMap("map"); + let text2 = doc2.getText("text"); + + array2.insert(0, [true]); + array2.insert(1, [false]); + array2.insert(2, [1]); + array2.insert(3, ["hello world"]); + map2.set("a", true); + map2.set("b", false); + map2.set("c", 1); + map2.set("d", "hello world"); + text2.insert(0, "a"); + text2.insert(1, "b"); + text2.insert(2, "c"); + + doc.applyUpdate(Buffer.from(Y.encodeStateAsUpdate(doc2))); + + let array = doc.getOrCreateArray("array"); + let map = doc.getOrCreateMap("map"); + let text = doc.getOrCreateText("text"); + + t.is(array.length, 4); + t.is(array.get(0), true); + t.is(array.get(1), false); + t.is(array.get(2), 1); + t.is(array.get(3), "hello world"); + t.is(map.length, 4); + t.is(map.get("a"), true); + t.is(map.get("b"), false); + t.is(map.get("c"), 1); + t.is(map.get("d"), "hello world"); + t.is(text.toString(), "abc"); +}); diff --git a/y-octo-node/tests/map.spec.mts b/y-octo-node/tests/map.spec.mts deleted file mode 100644 index c817294..0000000 --- a/y-octo-node/tests/map.spec.mts +++ /dev/null @@ -1,152 +0,0 @@ -import assert, { equal, deepEqual } from "node:assert"; -import { test } from "node:test"; - -import * as Y from "yjs"; -import * as YOcto from "../yocto"; - -test("map test", { concurrency: false }, async (t) => { - let client_id: number; - let doc: YOcto.Doc; - t.beforeEach(async () => { - client_id = (Math.random() * 100000) | 0; - doc = new YOcto.Doc(client_id); - }); - - t.afterEach(async () => { - client_id = -1; - // @ts-ignore - doc must not null in next range - doc = null; - }); - - await t.test("map should be created", () => { - let map = doc.getOrCreateMap("map"); - deepEqual(doc.keys, ["map"]); - equal(map.length, 0); - }); - - await t.test("map editing", () => { - let map = doc.getOrCreateMap("map"); - map.set("a", true); - map.set("b", false); - map.set("c", 1); - map.set("d", "hello world"); - equal(map.length, 4); - equal(map.get("a"), true); - equal(map.get("b"), false); - equal(map.get("c"), 1); - equal(map.get("d"), "hello world"); - equal(map.length, 4); - map.remove("b"); - equal(map.length, 3); - equal(map.get("d"), "hello world"); - }); - - await t.test("map should can be nested", () => { - let map = doc.getOrCreateMap("map"); - let sub = doc.createMap(); - map.set("sub", sub); - - sub.set("a", true); - sub.set("b", false); - sub.set("c", 1); - sub.set("d", "hello world"); - equal(sub.length, 4); - - let sub2 = map.get("sub"); - assert(sub2); - equal(sub2.get("a"), true); - equal(sub2.get("b"), false); - equal(sub2.get("c"), 1); - equal(sub2.get("d"), "hello world"); - equal(sub2.length, 4); - }); - - await t.test("y-octo to yjs compatibility test with nested type", () => { - let map = doc.getOrCreateMap("map"); - let sub_array = doc.createArray(); - let sub_map = doc.createMap(); - let sub_text = doc.createText(); - - map.set("array", sub_array); - map.set("map", sub_map); - map.set("text", sub_text); - - sub_array.insert(0, true); - sub_array.insert(1, false); - sub_array.insert(2, 1); - sub_array.insert(3, "hello world"); - sub_map.set("a", true); - sub_map.set("b", false); - sub_map.set("c", 1); - sub_map.set("d", "hello world"); - sub_text.insert(0, "a"); - sub_text.insert(1, "b"); - sub_text.insert(2, "c"); - - let doc2 = new Y.Doc(); - Y.applyUpdate(doc2, doc.encodeStateAsUpdateV1()); - - let map2 = doc2.getMap("map"); - let sub_array2 = map2.get("array") as Y.Array; - let sub_map2 = map2.get("map") as Y.Map; - let sub_text2 = map2.get("text") as Y.Text; - - assert(sub_array2); - equal(sub_array2.length, 4); - equal(sub_array2.get(0), true); - equal(sub_array2.get(1), false); - equal(sub_array2.get(2), 1); - equal(sub_array2.get(3), "hello world"); - assert(sub_map2); - equal(sub_map2.get("a"), true); - equal(sub_map2.get("b"), false); - equal(sub_map2.get("c"), 1); - equal(sub_map2.get("d"), "hello world"); - assert(sub_text2); - equal(sub_text2.toString(), "abc"); - }); - - await t.test("yjs to y-octo compatibility test with nested type", () => { - let doc2 = new Y.Doc(); - let map2 = doc2.getMap("map"); - let sub_array2 = new Y.Array(); - let sub_map2 = new Y.Map(); - let sub_text2 = new Y.Text(); - map2.set("array", sub_array2); - map2.set("map", sub_map2); - map2.set("text", sub_text2); - - sub_array2.insert(0, [true]); - sub_array2.insert(1, [false]); - sub_array2.insert(2, [1]); - sub_array2.insert(3, ["hello world"]); - sub_map2.set("a", true); - sub_map2.set("b", false); - sub_map2.set("c", 1); - sub_map2.set("d", "hello world"); - sub_text2.insert(0, "a"); - sub_text2.insert(1, "b"); - sub_text2.insert(2, "c"); - - doc.applyUpdate(Buffer.from(Y.encodeStateAsUpdate(doc2))); - - let map = doc.getOrCreateMap("map"); - let sub_array = map.get("array"); - let sub_map = map.get("map"); - let sub_text = map.get("text"); - - assert(sub_array); - equal(sub_array.length, 4); - equal(sub_array.get(0), true); - equal(sub_array.get(1), false); - equal(sub_array.get(2), 1); - equal(sub_array.get(3), "hello world"); - assert(sub_map); - equal(sub_map.get("a"), true); - equal(sub_map.get("b"), false); - equal(sub_map.get("c"), 1); - equal(sub_map.get("d"), "hello world"); - assert(sub_text); - equal(sub_text.toString(), "abc"); - }); -}); diff --git a/y-octo-node/tests/map.spec.ts b/y-octo-node/tests/map.spec.ts new file mode 100644 index 0000000..b355b22 --- /dev/null +++ b/y-octo-node/tests/map.spec.ts @@ -0,0 +1,149 @@ +import test from "ava"; + +import * as Y from "yjs"; +import * as YOcto from "../yocto"; + +let client_id: number; +let doc: YOcto.Doc; +test.beforeEach(async () => { + client_id = (Math.random() * 100000) | 0; + doc = new YOcto.Doc(client_id); +}); + +test.afterEach(async () => { + client_id = -1; + // @ts-ignore - doc must not null in next range + doc = null; +}); + +test("map should be created", (t) => { + let map = doc.getOrCreateMap("map"); + t.deepEqual(doc.keys, ["map"]); + t.is(map.length, 0); +}); + +test("map editing", (t) => { + let map = doc.getOrCreateMap("map"); + map.set("a", true); + map.set("b", false); + map.set("c", 1); + map.set("d", "hello world"); + t.is(map.length, 4); + t.is(map.get("a"), true); + t.is(map.get("b"), false); + t.is(map.get("c"), 1); + t.is(map.get("d"), "hello world"); + t.is(map.length, 4); + map.remove("b"); + t.is(map.length, 3); + t.is(map.get("d"), "hello world"); +}); + +test("map should can be nested", (t) => { + let map = doc.getOrCreateMap("map"); + let sub = doc.createMap(); + map.set("sub", sub); + + sub.set("a", true); + sub.set("b", false); + sub.set("c", 1); + sub.set("d", "hello world"); + t.is(sub.length, 4); + + let sub2 = map.get("sub"); + t.assert(sub2); + t.is(sub2.get("a"), true); + t.is(sub2.get("b"), false); + t.is(sub2.get("c"), 1); + t.is(sub2.get("d"), "hello world"); + t.is(sub2.length, 4); +}); + +test("y-octo to yjs compatibility test with nested type", (t) => { + let map = doc.getOrCreateMap("map"); + let sub_array = doc.createArray(); + let sub_map = doc.createMap(); + let sub_text = doc.createText(); + + map.set("array", sub_array); + map.set("map", sub_map); + map.set("text", sub_text); + + sub_array.insert(0, true); + sub_array.insert(1, false); + sub_array.insert(2, 1); + sub_array.insert(3, "hello world"); + sub_map.set("a", true); + sub_map.set("b", false); + sub_map.set("c", 1); + sub_map.set("d", "hello world"); + sub_text.insert(0, "a"); + sub_text.insert(1, "b"); + sub_text.insert(2, "c"); + + let doc2 = new Y.Doc(); + Y.applyUpdate(doc2, doc.encodeStateAsUpdateV1()); + + let map2 = doc2.getMap("map"); + let sub_array2 = map2.get("array") as Y.Array; + let sub_map2 = map2.get("map") as Y.Map; + let sub_text2 = map2.get("text") as Y.Text; + + t.assert(sub_array2); + t.is(sub_array2.length, 4); + t.is(sub_array2.get(0), true); + t.is(sub_array2.get(1), false); + t.is(sub_array2.get(2), 1); + t.is(sub_array2.get(3), "hello world"); + t.assert(sub_map2); + t.is(sub_map2.get("a"), true); + t.is(sub_map2.get("b"), false); + t.is(sub_map2.get("c"), 1); + t.is(sub_map2.get("d"), "hello world"); + t.assert(sub_text2); + t.is(sub_text2.toString(), "abc"); +}); + +test("yjs to y-octo compatibility test with nested type", (t) => { + let doc2 = new Y.Doc(); + let map2 = doc2.getMap("map"); + let sub_array2 = new Y.Array(); + let sub_map2 = new Y.Map(); + let sub_text2 = new Y.Text(); + map2.set("array", sub_array2); + map2.set("map", sub_map2); + map2.set("text", sub_text2); + + sub_array2.insert(0, [true]); + sub_array2.insert(1, [false]); + sub_array2.insert(2, [1]); + sub_array2.insert(3, ["hello world"]); + sub_map2.set("a", true); + sub_map2.set("b", false); + sub_map2.set("c", 1); + sub_map2.set("d", "hello world"); + sub_text2.insert(0, "a"); + sub_text2.insert(1, "b"); + sub_text2.insert(2, "c"); + + doc.applyUpdate(Buffer.from(Y.encodeStateAsUpdate(doc2))); + + let map = doc.getOrCreateMap("map"); + let sub_array = map.get("array"); + let sub_map = map.get("map"); + let sub_text = map.get("text"); + + t.assert(sub_array); + t.is(sub_array.length, 4); + t.is(sub_array.get(0), true); + t.is(sub_array.get(1), false); + t.is(sub_array.get(2), 1); + t.is(sub_array.get(3), "hello world"); + t.assert(sub_map); + t.is(sub_map.get("a"), true); + t.is(sub_map.get("b"), false); + t.is(sub_map.get("c"), 1); + t.is(sub_map.get("d"), "hello world"); + t.assert(sub_text); + t.is(sub_text.toString(), "abc"); +}); diff --git a/y-octo-node/tests/text.spec.mts b/y-octo-node/tests/text.spec.mts deleted file mode 100644 index ebcf588..0000000 --- a/y-octo-node/tests/text.spec.mts +++ /dev/null @@ -1,54 +0,0 @@ -import assert, { equal, deepEqual } from "node:assert"; -import { test } from "node:test"; - -import * as YOcto from "../yocto"; - -test("text test", { concurrency: false }, async (t) => { - let client_id: number; - let doc: YOcto.Doc; - t.beforeEach(async () => { - client_id = (Math.random() * 100000) | 0; - doc = new YOcto.Doc(client_id); - }); - - t.afterEach(async () => { - client_id = -1; - // @ts-ignore - doc must not null in next range - doc = null; - }); - - await t.test("text should be created", () => { - let text = doc.getOrCreateText("text"); - deepEqual(doc.keys, ["text"]); - equal(text.len, 0); - }); - - await t.test("text editing", () => { - let text = doc.getOrCreateText("text"); - text.insert(0, "a"); - text.insert(1, "b"); - text.insert(2, "c"); - equal(text.toString(), "abc"); - text.remove(0, 1); - equal(text.toString(), "bc"); - text.remove(1, 1); - equal(text.toString(), "b"); - text.remove(0, 1); - equal(text.toString(), ""); - }); - - await t.test("sub text should can edit", () => { - let map = doc.getOrCreateMap("map"); - let sub = doc.createText(); - map.set("sub", sub); - - sub.insert(0, "a"); - sub.insert(1, "b"); - sub.insert(2, "c"); - equal(sub.toString(), "abc"); - - let sub2 = map.get("sub"); - assert(sub2); - equal(sub2.toString(), "abc"); - }); -}); diff --git a/y-octo-node/tests/text.spec.ts b/y-octo-node/tests/text.spec.ts new file mode 100644 index 0000000..e0f8bab --- /dev/null +++ b/y-octo-node/tests/text.spec.ts @@ -0,0 +1,51 @@ +import test from "ava"; + +import * as YOcto from "../yocto"; + +let client_id: number; +let doc: YOcto.Doc; +test.beforeEach(async () => { + client_id = (Math.random() * 100000) | 0; + doc = new YOcto.Doc(client_id); +}); + +test.afterEach(async () => { + client_id = -1; + // @ts-ignore - doc must not null in next range + doc = null; +}); + +test("text should be created", (t) => { + let text = doc.getOrCreateText("text"); + t.deepEqual(doc.keys, ["text"]); + t.is(text.len, 0); +}); + +test("text editing", (t) => { + let text = doc.getOrCreateText("text"); + text.insert(0, "a"); + text.insert(1, "b"); + text.insert(2, "c"); + t.is(text.toString(), "abc"); + text.remove(0, 1); + t.is(text.toString(), "bc"); + text.remove(1, 1); + t.is(text.toString(), "b"); + text.remove(0, 1); + t.is(text.toString(), ""); +}); + +test("sub text should can edit", (t) => { + let map = doc.getOrCreateMap("map"); + let sub = doc.createText(); + map.set("sub", sub); + + sub.insert(0, "a"); + sub.insert(1, "b"); + sub.insert(2, "c"); + t.is(sub.toString(), "abc"); + + let sub2 = map.get("sub"); + t.assert(sub2); + t.is(sub2.toString(), "abc"); +}); diff --git a/y-octo-node/tests/yjs/testHelper.ts b/y-octo-node/tests/yjs/testHelper.ts index aec05b2..e2266c8 100644 --- a/y-octo-node/tests/yjs/testHelper.ts +++ b/y-octo-node/tests/yjs/testHelper.ts @@ -5,7 +5,7 @@ import * as decoding from "lib0/decoding"; import * as syncProtocol from "y-protocols/sync"; import * as object from "lib0/object"; import * as map from "lib0/map"; -import * as Y from "../../index"; +import * as Y from "../../yocto"; if (typeof window !== "undefined") { // @ts-ignore @@ -99,6 +99,7 @@ export class TestYOctoInstance extends Y.Doc { * Also initiate sync with all clients. */ connect() { + return; if (!this.tc.onlineConns.has(this)) { this.tc.onlineConns.add(this); const encoder = encoding.createEncoder(); @@ -261,12 +262,11 @@ type InitResult = { // xml1: Y.XmlElement; // xml2: Y.XmlElement; }; -export const init = ( - tc: { prng: any }, +export const init = ( + gen: prng.PRNG, { users = 5 }: { users?: number } = {}, initTestObject?: any, ): InitResult => { - const gen = tc.prng; // choose an encoding approach at random if (prng.bool(gen)) { useV2Encoding(); @@ -483,13 +483,12 @@ export const compareStructStores = ( * @param {InitTestObjectCallback} [initTestObject] */ export const applyRandomTests = ( - tc: { prng: any }, + gen: prng.PRNG, mods: unknown[], iterations: number, initTestObject?: any, ) => { - const gen = tc.prng; - const result = init(tc, { users: 5 }, initTestObject); + const result = init(gen, { users: 5 }, initTestObject); const { testConnector, users } = result; for (let i = 0; i < iterations; i++) { if (prng.int32(gen, 0, 100) <= 2) { diff --git a/y-octo-node/tests/yjs/yarray.spec.mts b/y-octo-node/tests/yjs/yarray.spec.mts deleted file mode 100644 index a55335d..0000000 --- a/y-octo-node/tests/yjs/yarray.spec.mts +++ /dev/null @@ -1,587 +0,0 @@ -import assert, { equal, deepEqual, doesNotThrow } from "node:assert"; -import { test } from "node:test"; - -import { init, compare, applyRandomTests } from "./testHelper"; - -import * as Y from "../../yocto"; -import * as t from "lib0/testing"; -import * as prng from "lib0/prng"; -import * as math from "lib0/math"; - -test("yjs array test", { concurrency: false }, async (t) => { - await t.test("testBasicUpdate", () => { - const doc1 = new Y.Doc(); - const doc2 = new Y.Doc(); - doc1.getOrCreateArray("array").insert(0, ["hi"]); - const update = Y.encodeStateAsUpdate(doc1); - Y.applyUpdate(doc2, update); - deepEqual(doc2.getOrCreateArray("array").toArray(), ["hi"]); - }); - - await t.test("testSlice", () => { - const doc1 = new Y.Doc(); - const arr = doc1.getOrCreateArray("array"); - arr.insert(0, [1, 2, 3]); - deepEqual(arr.slice(0), [1, 2, 3]); - deepEqual(arr.slice(1), [2, 3]); - deepEqual(arr.slice(0, -1), [1, 2]); - arr.insert(0, [0]); - deepEqual(arr.slice(0), [0, 1, 2, 3]); - deepEqual(arr.slice(0, 2), [0, 1]); - }); - - const testArrayFrom = () => { - const doc1 = new Y.Doc(); - const db1 = doc1.getOrCreateMap("root"); - const nestedArray1 = Y.Array.from([0, 1, 2]); - db1.set("array", nestedArray1); - deepEqual(nestedArray1.toArray(), [0, 1, 2]); - }; - - /** - * Debugging yjs#297 - a critical bug connected to the search-marker approach - */ - await t.test("testLengthIssue", () => { - const doc1 = new Y.Doc(); - const arr = doc1.getOrCreateArray("array"); - arr.push([0, 1, 2, 3]); - arr.delete(0); - arr.insert(0, [0]); - equal(arr.length, arr.toArray().length); - doc1.transact(() => { - arr.delete(1); - equal(arr.length, arr.toArray().length); - arr.insert(1, [1]); - equal(arr.length, arr.toArray().length); - arr.delete(2); - equal(arr.length, arr.toArray().length); - arr.insert(2, [2]); - equal(arr.length, arr.toArray().length); - }); - equal(arr.length, arr.toArray().length); - arr.delete(1); - equal(arr.length, arr.toArray().length); - arr.insert(1, [1]); - equal(arr.length, arr.toArray().length); - }); - - /** - * Debugging yjs#314 - */ - await t.test("testLengthIssue2", () => { - const doc = new Y.Doc(); - const next = doc.createArray(); - doc.transact(() => { - next.insert(0, ["group2"]); - }); - doc.transact(() => { - next.insert(1, ["rectangle3"]); - }); - doc.transact(() => { - next.delete(0); - next.insert(0, ["rectangle3"]); - }); - next.delete(1); - doc.transact(() => { - next.insert(1, ["ellipse4"]); - }); - doc.transact(() => { - next.insert(2, ["ellipse3"]); - }); - doc.transact(() => { - next.insert(3, ["ellipse2"]); - }); - doc.transact(() => { - doc.transact(() => { - doesNotThrow(() => { - next.insert(5, ["rectangle2"]); - }); - next.insert(4, ["rectangle2"]); - }); - doc.transact(() => { - // this should not throw an error message - next.delete(4); - }); - }); - console.log(next.toArray()); - }); - - const testDeleteInsert = () => { - const { users, array0 } = init(tc, { users: 2 }); - assert(array0); - array0.delete(0, 0); - doesNotThrow(() => { - array0.delete(1, 1); - }, "Does not throw when deleting zero elements with position 0"); - array0.insert(0, ["A"]); - doesNotThrow(() => { - array0.delete(1, 0); - }, "Does not throw when deleting zero elements with valid position 1"); - compare(users); - }; - - const testInsertThreeElementsTryRegetProperty = (tc: t.TestCase) => { - const { testConnector, users, array0, array1 } = init(tc, { users: 2 }); - assert(array0); - assert(array1); - array0.insert(0, [1, true, false]); - deepEqual(array0.toJSON(), [1, true, false], ".toJSON() works"); - testConnector.flushAllMessages(); - deepEqual(array1.toJSON(), [1, true, false], ".toJSON() works after sync"); - compare(users); - }; - - const testConcurrentInsertWithThreeConflicts = (tc: t.TestCase) => { - const { users, array0, array1, array2 } = init(tc, { users: 3 }); - assert(array0); - assert(array1); - assert(array2); - array0.insert(0, [0]); - array1.insert(0, [1]); - array2.insert(0, [2]); - compare(users); - }; - - const testConcurrentInsertDeleteWithThreeConflicts = (tc: t.TestCase) => { - const { testConnector, users, array0, array1, array2 } = init(tc, { - users: 3, - }); - assert(array0); - assert(array1); - assert(array2); - array0.insert(0, ["x", "y", "z"]); - testConnector.flushAllMessages(); - array0.insert(1, [0]); - array1.delete(0); - array1.delete(1, 1); - array2.insert(1, [2]); - compare(users); - }; - - const testInsertionsInLateSync = (tc: t.TestCase) => { - const { testConnector, users, array0, array1, array2 } = init(tc, { - users: 3, - }); - assert(array0); - assert(array1); - assert(array2); - array0.insert(0, ["x", "y"]); - testConnector.flushAllMessages(); - users[1].disconnect(); - users[2].disconnect(); - array0.insert(1, ["user0"]); - array1.insert(1, ["user1"]); - array2.insert(1, ["user2"]); - users[1].connect(); - users[2].connect(); - testConnector.flushAllMessages(); - compare(users); - }; - - const testDisconnectReallyPreventsSendingMessages = (tc: t.TestCase) => { - const { testConnector, users, array0, array1 } = init(tc, { users: 3 }); - assert(array0); - assert(array1); - array0.insert(0, ["x", "y"]); - testConnector.flushAllMessages(); - users[1].disconnect(); - users[2].disconnect(); - array0.insert(1, ["user0"]); - array1.insert(1, ["user1"]); - deepEqual(array0.toJSON(), ["x", "user0", "y"]); - deepEqual(array1.toJSON(), ["x", "user1", "y"]); - users[1].connect(); - users[2].connect(); - compare(users); - }; - - const testDeletionsInLateSync = (tc: t.TestCase) => { - const { testConnector, users, array0, array1 } = init(tc, { users: 2 }); - assert(array0); - assert(array1); - array0.insert(0, ["x", "y"]); - testConnector.flushAllMessages(); - users[1].disconnect(); - array1.delete(1, 1); - array0.delete(0, 2); - users[1].connect(); - compare(users); - }; - - const testInsertThenMergeDeleteOnSync = (tc: t.TestCase) => { - const { testConnector, users, array0, array1 } = init(tc, { users: 2 }); - assert(array0); - assert(array1); - array0.insert(0, ["x", "y", "z"]); - testConnector.flushAllMessages(); - users[0].disconnect(); - array1.delete(0, 3); - users[0].connect(); - compare(users); - }; - - const testInsertAndDeleteEvents = (tc: t.TestCase) => { - const { array0, users } = init(tc, { users: 2 }); - assert(array0); - let event: Record | null = null; - array0.observe((e) => { - event = e; - }); - array0.insert(0, [0, 1, 2]); - assert(event !== null); - event = null; - array0.delete(0); - assert(event !== null); - event = null; - array0.delete(0, 2); - assert(event !== null); - event = null; - compare(users); - }; - - const testNestedObserverEvents = (tc: t.TestCase) => { - const { array0, users } = init(tc, { users: 2 }); - assert(array0); - const vals: number[] = []; - array0.observe((e) => { - if (array0.length === 1) { - // inserting, will call this observer again - // we expect that this observer is called after this event handler finishedn - array0.insert(1, [1]); - vals.push(0); - } else { - // this should be called the second time an element is inserted (above case) - vals.push(1); - } - }); - array0.insert(0, [0]); - deepEqual(vals, [0, 1]); - deepEqual(array0.toArray(), [0, 1]); - compare(users); - }; - - const testInsertAndDeleteEventsForTypes = (tc: t.TestCase) => { - const { array0, users } = init(tc, { users: 2 }); - assert(array0); - let event: Record | null = null; - array0.observe((e) => { - event = e; - }); - array0.insert(0, [new Y.Array()]); - assert(event !== null); - event = null; - array0.delete(0); - assert(event !== null); - event = null; - compare(users); - }; - - /** - * This issue has been reported in https://discuss.yjs.dev/t/order-in-which-events-yielded-by-observedeep-should-be-applied/261/2 - * - * Deep observers generate multiple events. When an array added at item at, say, position 0, - * and item 1 changed then the array-add event should fire first so that the change event - * path is correct. A array binding might lead to an inconsistent state otherwise. - */ - const testObserveDeepEventOrder = (tc: t.TestCase) => { - const { array0, users } = init(tc, { users: 2 }); - assert(array0); - let events: any[] = []; - array0.observeDeep((e) => { - events = e; - }); - array0.insert(0, [new Y.Map()]); - users[0].transact(() => { - array0.get(0).set("a", "a"); - array0.insert(0, [0]); - }); - for (let i = 1; i < events.length; i++) { - assert( - events[i - 1].path.length <= events[i].path.length, - "path size increases, fire top-level events first", - ); - } - }; - - /** - * Correct index when computing event.path in observeDeep - https://github.com/yjs/yjs/issues/457 - */ - const testObservedeepIndexes = () => { - const doc = new Y.Doc(); - const map = doc.createMap(); - // 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()]); - let eventPath: any[] = []; - 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); - deepEqual(eventPath, ["my-array", 3]); - }; - - const testChangeEvent = (tc: t.TestCase) => { - const { array0, users } = init(tc, { users: 2 }); - assert(array0); - let changes: any = null; - array0.observe((e) => { - changes = e.changes; - }); - const newArr = new Y.Array(); - array0.insert(0, [newArr, 4, "dtrn"]); - assert( - changes !== null && - changes.added.size === 2 && - changes.deleted.size === 0, - ); - deepEqual(changes.delta, [{ insert: [newArr, 4, "dtrn"] }]); - changes = null; - array0.delete(0, 2); - assert( - changes !== null && - changes.added.size === 0 && - changes.deleted.size === 2, - ); - deepEqual(changes.delta, [{ delete: 2 }]); - changes = null; - array0.insert(1, [0.1]); - assert( - changes !== null && - changes.added.size === 1 && - changes.deleted.size === 0, - ); - deepEqual(changes.delta, [{ retain: 1 }, { insert: [0.1] }]); - compare(users); - }; - - const testInsertAndDeleteEventsForTypes2 = (tc: t.TestCase) => { - const { array0, users } = init(tc, { users: 2 }); - assert(array0); - const events: Record[] = []; - array0.observe((e) => { - events.push(e); - }); - array0.insert(0, ["hi", new Y.Map()]); - equal( - events.length, - 1, - "Event is triggered exactly once for insertion of two elements", - ); - array0.delete(1); - equal(events.length, 2, "Event is triggered exactly once for deletion"); - compare(users); - }; - - /** - * This issue has been reported here https://github.com/yjs/yjs/issues/155 - * @param {t.TestCase} tc - */ - const testNewChildDoesNotEmitEventInTransaction = (tc: t.TestCase) => { - const { array0, users } = init(tc, { users: 2 }); - assert(array0); - let fired = false; - users[0].transact(() => { - const newMap = new Y.Map(); - newMap.observe(() => { - fired = true; - }); - array0.insert(0, [newMap]); - newMap.set("tst", 42); - }); - assert(!fired, "Event does not trigger"); - }; - - const testGarbageCollector = (tc: t.TestCase) => { - const { testConnector, users, array0 } = init(tc, { users: 3 }); - assert(array0); - array0.insert(0, ["x", "y", "z"]); - testConnector.flushAllMessages(); - users[0].disconnect(); - array0.delete(0, 3); - users[0].connect(); - testConnector.flushAllMessages(); - compare(users); - }; - - /** - * @param {t.TestCase} tc - */ - const testEventTargetIsSetCorrectlyOnLocal = (tc: t.TestCase) => { - const { array0, users } = init(tc, { users: 3 }); - assert(array0); - let event: any; - array0.observe((e) => { - event = e; - }); - array0.insert(0, ["stuff"]); - assert(event.target === array0, '"target" property is set correctly'); - compare(users); - }; - - const testEventTargetIsSetCorrectlyOnRemote = (tc: t.TestCase) => { - const { testConnector, array0, array1, users } = init(tc, { users: 3 }); - assert(array0); - assert(array1); - let event: any; - array0.observe((e) => { - event = e; - }); - array1.insert(0, ["stuff"]); - testConnector.flushAllMessages(); - assert(event.target === array0, '"target" property is set correctly'); - compare(users); - }; - - const testIteratingArrayContainingTypes = () => { - const y = new Y.Doc(); - const arr = y.getOrCreateArray("arr"); - const numItems = 10; - for (let i = 0; i < numItems; i++) { - const map = new Y.Map(); - map.set("value", i); - arr.push([map]); - } - let cnt = 0; - for (const item of arr.iter()) { - assert(item.get("value") === cnt++, "value is correct"); - } - y.destroy(); - }; - - let _uniqueNumber = 0; - const getUniqueNumber = () => _uniqueNumber++; - - const arrayTransactions: Array< - (arg0: Y.Doc, arg1: prng.PRNG, arg2: any) => void - > = [ - function insert(user: Y.Doc, gen: prng.PRNG) { - const yarray = user.getOrCreateArray("array"); - const uniqueNumber = getUniqueNumber(); - const content: number[] = []; - const len = prng.int32(gen, 1, 4); - for (let i = 0; i < len; i++) { - content.push(uniqueNumber); - } - const pos = prng.int32(gen, 0, yarray.length); - const oldContent = yarray.toArray(); - yarray.insert(pos, content); - oldContent.splice(pos, 0, ...content); - deepEqual(yarray.toArray(), oldContent); // we want to make sure that fastSearch markers insert at the correct position - }, - function insertTypeArray(user: Y.Doc, gen: prng.PRNG) { - const yarray = user.getOrCreateArray("array"); - const pos = prng.int32(gen, 0, yarray.length); - yarray.insert(pos, [new Y.Array()]); - const array2 = yarray.get(pos); - array2.insert(0, [1, 2, 3, 4]); - }, - function insertTypeMap(user: Y.Doc, gen: prng.PRNG) { - const yarray = user.getOrCreateArray("array"); - const pos = prng.int32(gen, 0, yarray.length); - yarray.insert(pos, [new Y.Map()]); - const map = yarray.get(pos); - map.set("someprop", 42); - map.set("someprop", 43); - map.set("someprop", 44); - }, - function insertTypeNull(user: Y.Doc, gen: prng.PRNG) { - const yarray = user.getOrCreateArray("array"); - const pos = prng.int32(gen, 0, yarray.length); - yarray.insert(pos, [null]); - }, - function _delete(user: Y.Doc, gen: prng.PRNG) { - const yarray = user.getOrCreateArray("array"); - const length = yarray.length; - if (length > 0) { - let somePos = prng.int32(gen, 0, length - 1); - let delLength = prng.int32(gen, 1, math.min(2, length - somePos)); - if (prng.bool(gen)) { - const type = yarray.get(somePos); - if (type instanceof Y.Array && type.length > 0) { - somePos = prng.int32(gen, 0, type.length - 1); - delLength = prng.int32(gen, 0, math.min(2, type.length - somePos)); - type.delete(somePos, delLength); - } - } else { - const oldContent = yarray.toArray(); - yarray.delete(somePos, delLength); - oldContent.splice(somePos, delLength); - deepEqual(yarray.toArray(), oldContent); - } - } - }, - ]; - - /** - * @param {t.TestCase} tc - */ - const testRepeatGeneratingYarrayTests6 = (tc: t.TestCase) => { - applyRandomTests(tc, arrayTransactions, 6); - }; - - const testRepeatGeneratingYarrayTests40 = (tc: t.TestCase) => { - applyRandomTests(tc, arrayTransactions, 40); - }; - - const testRepeatGeneratingYarrayTests42 = (tc: t.TestCase) => { - applyRandomTests(tc, arrayTransactions, 42); - }; - - const testRepeatGeneratingYarrayTests43 = (tc: t.TestCase) => { - applyRandomTests(tc, arrayTransactions, 43); - }; - - const testRepeatGeneratingYarrayTests44 = (tc: t.TestCase) => { - applyRandomTests(tc, arrayTransactions, 44); - }; - - const testRepeatGeneratingYarrayTests45 = (tc: t.TestCase) => { - applyRandomTests(tc, arrayTransactions, 45); - }; - - const testRepeatGeneratingYarrayTests46 = (tc: t.TestCase) => { - applyRandomTests(tc, arrayTransactions, 46); - }; - - const testRepeatGeneratingYarrayTests300 = (tc: t.TestCase) => { - applyRandomTests(tc, arrayTransactions, 300); - }; - - const testRepeatGeneratingYarrayTests400 = (tc: t.TestCase) => { - applyRandomTests(tc, arrayTransactions, 400); - }; - - const testRepeatGeneratingYarrayTests500 = (tc: t.TestCase) => { - applyRandomTests(tc, arrayTransactions, 500); - }; - - const testRepeatGeneratingYarrayTests600 = (tc: t.TestCase) => { - applyRandomTests(tc, arrayTransactions, 600); - }; - - const testRepeatGeneratingYarrayTests1000 = (tc: t.TestCase) => { - applyRandomTests(tc, arrayTransactions, 1000); - }; - - const testRepeatGeneratingYarrayTests1800 = (tc: t.TestCase) => { - applyRandomTests(tc, arrayTransactions, 1800); - }; - - const testRepeatGeneratingYarrayTests3000 = (tc: t.TestCase) => { - t.skip(!t.production); - applyRandomTests(tc, arrayTransactions, 3000); - }; - - const testRepeatGeneratingYarrayTests5000 = (tc: t.TestCase) => { - t.skip(!t.production); - applyRandomTests(tc, arrayTransactions, 5000); - }; - - const testRepeatGeneratingYarrayTests30000 = (tc: t.TestCase) => { - t.skip(!t.production); - applyRandomTests(tc, arrayTransactions, 30000); - }; -}); diff --git a/y-octo-node/tests/yjs/yarray.spec.ts b/y-octo-node/tests/yjs/yarray.spec.ts new file mode 100644 index 0000000..28fc8fa --- /dev/null +++ b/y-octo-node/tests/yjs/yarray.spec.ts @@ -0,0 +1,579 @@ +import test from "ava"; + +import { init, compare, applyRandomTests } from "./testHelper"; + +import * as Y from "../../yocto"; +import * as prng from "lib0/prng"; +import * as math from "lib0/math"; +import { randomInt } from "node:crypto"; +import assert, { deepEqual } from "node:assert"; + +const production = false; + +let gen: prng.PRNG; +test.beforeEach(() => { + gen = prng.create(randomInt(0, 0xffffffff)); +}); + +test("testBasicUpdate", (t) => { + const doc1 = new Y.Doc(); + const doc2 = new Y.Doc(); + doc1.getOrCreateArray("array").insert(0, ["hi"]); + const update = Y.encodeStateAsUpdate(doc1); + Y.applyUpdate(doc2, update); + t.deepEqual(doc2.getOrCreateArray("array").toArray(), ["hi"]); +}); + +test("testSlice", (t) => { + const doc1 = new Y.Doc(); + const arr = doc1.getOrCreateArray("array"); + arr.insert(0, [1, 2, 3]); + t.deepEqual(arr.slice(0), [1, 2, 3]); + t.deepEqual(arr.slice(1), [2, 3]); + t.deepEqual(arr.slice(0, -1), [1, 2]); + arr.insert(0, [0]); + t.deepEqual(arr.slice(0), [0, 1, 2, 3]); + t.deepEqual(arr.slice(0, 2), [0, 1]); +}); + +test("testArrayFrom", (t) => { + const doc1 = new Y.Doc(); + const db1 = doc1.getOrCreateMap("root"); + const nestedArray1 = Y.Array.from([0, 1, 2]); + db1.set("array", nestedArray1); + t.deepEqual(nestedArray1.toArray(), [0, 1, 2]); +}); + +/** + * Debugging yjs#297 - a critical bug connected to the search-marker approach + */ +test("testLengthIssue", (t) => { + const doc1 = new Y.Doc(); + const arr = doc1.getOrCreateArray("array"); + arr.push([0, 1, 2, 3]); + arr.delete(0); + arr.insert(0, [0]); + t.is(arr.length, arr.toArray().length); + doc1.transact(() => { + arr.delete(1); + t.is(arr.length, arr.toArray().length); + arr.insert(1, [1]); + t.is(arr.length, arr.toArray().length); + arr.delete(2); + t.is(arr.length, arr.toArray().length); + arr.insert(2, [2]); + t.is(arr.length, arr.toArray().length); + }); + t.is(arr.length, arr.toArray().length); + arr.delete(1); + t.is(arr.length, arr.toArray().length); + arr.insert(1, [1]); + t.is(arr.length, arr.toArray().length); +}); + +/** + * Debugging yjs#314 + */ +test("testLengthIssue2", (t) => { + const doc = new Y.Doc(); + const next = doc.createArray(); + doc.transact(() => { + next.insert(0, ["group2"]); + }); + doc.transact(() => { + next.insert(1, ["rectangle3"]); + }); + doc.transact(() => { + next.delete(0); + next.insert(0, ["rectangle3"]); + }); + next.delete(1); + doc.transact(() => { + next.insert(1, ["ellipse4"]); + }); + doc.transact(() => { + next.insert(2, ["ellipse3"]); + }); + doc.transact(() => { + next.insert(3, ["ellipse2"]); + }); + doc.transact(() => { + doc.transact(() => { + t.notThrows(() => { + next.insert(5, ["rectangle2"]); + }); + next.insert(4, ["rectangle2"]); + }); + doc.transact(() => { + // this should not throw an error message + next.delete(4); + }); + }); + console.log(next.toArray()); +}); + +test("testDeleteInsert", (t) => { + const { users, array0 } = init(gen, { users: 2 }); + assert(array0); + array0.delete(0, 0); + t.notThrows(() => { + array0.delete(1, 1); + }, "Does not throw when deleting zero elements with position 0"); + array0.insert(0, ["A"]); + t.notThrows(() => { + array0.delete(1, 0); + }, "Does not throw when deleting zero elements with valid position 1"); + compare(users); +}); + +test("testInsertThreeElementsTryRegetProperty", (t) => { + const { testConnector, users, array0, array1 } = init(gen, { users: 2 }); + assert(array0); + assert(array1); + array0.insert(0, [1, true, false]); + t.deepEqual(array0.toJSON(), [1, true, false], ".toJSON() works"); + testConnector.flushAllMessages(); + t.deepEqual(array1.toJSON(), [1, true, false], ".toJSON() works after sync"); + compare(users); +}); + +test("testConcurrentInsertWithThreeConflicts", (t) => { + const { users, array0, array1, array2 } = init(gen, { users: 3 }); + assert(array0); + assert(array1); + assert(array2); + array0.insert(0, [0]); + array1.insert(0, [1]); + array2.insert(0, [2]); + compare(users); +}); + +test("testConcurrentInsertDeleteWithThreeConflicts", (t) => { + const { testConnector, users, array0, array1, array2 } = init(gen, { + users: 3, + }); + assert(array0); + assert(array1); + assert(array2); + array0.insert(0, ["x", "y", "z"]); + testConnector.flushAllMessages(); + array0.insert(1, [0]); + array1.delete(0); + array1.delete(1, 1); + array2.insert(1, [2]); + compare(users); +}); + +test("testInsertionsInLateSync", (t) => { + const { testConnector, users, array0, array1, array2 } = init(gen, { + users: 3, + }); + assert(array0); + assert(array1); + assert(array2); + array0.insert(0, ["x", "y"]); + testConnector.flushAllMessages(); + users[1].disconnect(); + users[2].disconnect(); + array0.insert(1, ["user0"]); + array1.insert(1, ["user1"]); + array2.insert(1, ["user2"]); + users[1].connect(); + users[2].connect(); + testConnector.flushAllMessages(); + compare(users); +}); + +test("testDisconnectReallyPreventsSendingMessages", (t) => { + const { testConnector, users, array0, array1 } = init(gen, { users: 3 }); + assert(array0); + assert(array1); + array0.insert(0, ["x", "y"]); + testConnector.flushAllMessages(); + users[1].disconnect(); + users[2].disconnect(); + array0.insert(1, ["user0"]); + array1.insert(1, ["user1"]); + t.deepEqual(array0.toJSON(), ["x", "user0", "y"]); + t.deepEqual(array1.toJSON(), ["x", "user1", "y"]); + users[1].connect(); + users[2].connect(); + compare(users); +}); + +test("testDeletionsInLateSync", (t) => { + const { testConnector, users, array0, array1 } = init(gen, { users: 2 }); + assert(array0); + assert(array1); + array0.insert(0, ["x", "y"]); + testConnector.flushAllMessages(); + users[1].disconnect(); + array1.delete(1, 1); + array0.delete(0, 2); + users[1].connect(); + compare(users); +}); + +test("testInsertThenMergeDeleteOnSync", (t) => { + const { testConnector, users, array0, array1 } = init(gen, { users: 2 }); + assert(array0); + assert(array1); + array0.insert(0, ["x", "y", "z"]); + testConnector.flushAllMessages(); + users[0].disconnect(); + array1.delete(0, 3); + users[0].connect(); + compare(users); +}); + +test("testInsertAndDeleteEvents", (t) => { + const { array0, users } = init(gen, { users: 2 }); + assert(array0); + let event: Record | null = null; + array0.observe((e) => { + event = e; + }); + array0.insert(0, [0, 1, 2]); + assert(event !== null); + event = null; + array0.delete(0); + assert(event !== null); + event = null; + array0.delete(0, 2); + assert(event !== null); + event = null; + compare(users); +}); + +test("testNestedObserverEvents", (t) => { + const { array0, users } = init(gen, { users: 2 }); + assert(array0); + const vals: number[] = []; + array0.observe((e) => { + if (array0.length === 1) { + // inserting, will call this observer again + // we expect that this observer is called after this event handler finishedn + array0.insert(1, [1]); + vals.push(0); + } else { + // this should be called the second time an element is inserted (above case) + vals.push(1); + } + }); + array0.insert(0, [0]); + t.deepEqual(vals, [0, 1]); + t.deepEqual(array0.toArray(), [0, 1]); + compare(users); +}); + +test("testInsertAndDeleteEventsForTypes", (t) => { + const { array0, users } = init(gen, { users: 2 }); + assert(array0); + let event: Record | null = null; + array0.observe((e) => { + event = e; + }); + array0.insert(0, [new Y.Array()]); + assert(event !== null); + event = null; + array0.delete(0); + assert(event !== null); + event = null; + compare(users); +}); + +/** + * This issue has been reported in https://discuss.yjs.dev/t/order-in-which-events-yielded-by-observedeep-should-be-applied/261/2 + * + * Deep observers generate multiple events. When an array added at item at, say, position 0, + * and item 1 changed then the array-add event should fire first so that the change event + * path is correct. A array binding might lead to an inconsistent state otherwise. + */ +test("testObserveDeepEventOrder", (t) => { + const { array0, users } = init(gen, { users: 2 }); + assert(array0); + let events: any[] = []; + array0.observeDeep((e) => { + events = e; + }); + array0.insert(0, [new Y.Map()]); + users[0].transact(() => { + array0.get(0).set("a", "a"); + array0.insert(0, [0]); + }); + for (let i = 1; i < events.length; i++) { + assert( + events[i - 1].path.length <= events[i].path.length, + "path size increases, fire top-level events first", + ); + } +}); + +/** + * Correct index when computing event.path in observeDeep - https://github.com/yjs/yjs/issues/457 + */ +test("testObservedeepIndexes", (t) => { + const doc = new Y.Doc(); + const map = doc.createMap(); + // 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()]); + let eventPath: any[] = []; + 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.deepEqual(eventPath, ["my-array", 3]); +}); + +test("testChangeEvent", (t) => { + const { array0, users } = init(gen, { users: 2 }); + assert(array0); + let changes: any = null; + array0.observe((e) => { + changes = e.changes; + }); + const newArr = new Y.Array(); + array0.insert(0, [newArr, 4, "dtrn"]); + assert( + changes !== null && changes.added.size === 2 && changes.deleted.size === 0, + ); + t.deepEqual(changes.delta, [{ insert: [newArr, 4, "dtrn"] }]); + changes = null; + array0.delete(0, 2); + assert( + changes !== null && changes.added.size === 0 && changes.deleted.size === 2, + ); + t.deepEqual(changes.delta, [{ delete: 2 }]); + changes = null; + array0.insert(1, [0.1]); + assert( + changes !== null && changes.added.size === 1 && changes.deleted.size === 0, + ); + t.deepEqual(changes.delta, [{ retain: 1 }, { insert: [0.1] }]); + compare(users); +}); + +test("testInsertAndDeleteEventsForTypes2", (t) => { + const { array0, users } = init(gen, { users: 2 }); + assert(array0); + const events: Record[] = []; + array0.observe((e) => { + events.push(e); + }); + array0.insert(0, ["hi", new Y.Map()]); + t.is( + events.length, + 1, + "Event is triggered exactly once for insertion of two elements", + ); + array0.delete(1); + t.is(events.length, 2, "Event is triggered exactly once for deletion"); + compare(users); +}); + +/** + * This issue has been reported here https://github.com/yjs/yjs/issues/155 + */ +test("testNewChildDoesNotEmitEventInTransaction", (t) => { + const { array0, users } = init(gen, { users: 2 }); + assert(array0); + let fired = false; + users[0].transact(() => { + const newMap = new Y.Map(); + newMap.observe(() => { + fired = true; + }); + array0.insert(0, [newMap]); + newMap.set("tst", 42); + }); + assert(!fired, "Event does not trigger"); +}); + +test("testGarbageCollector", (t) => { + const { testConnector, users, array0 } = init(gen, { users: 3 }); + assert(array0); + array0.insert(0, ["x", "y", "z"]); + testConnector.flushAllMessages(); + users[0].disconnect(); + array0.delete(0, 3); + users[0].connect(); + testConnector.flushAllMessages(); + compare(users); +}); + +test("testEventTargetIsSetCorrectlyOnLocal", (t) => { + const { array0, users } = init(gen, { users: 3 }); + assert(array0); + let event: any; + array0.observe((e) => { + event = e; + }); + array0.insert(0, ["stuff"]); + assert(event.target === array0, '"target" property is set correctly'); + compare(users); +}); + +test("testEventTargetIsSetCorrectlyOnRemote", (t) => { + const { testConnector, array0, array1, users } = init(gen, { users: 3 }); + assert(array0); + assert(array1); + let event: any; + array0.observe((e) => { + event = e; + }); + array1.insert(0, ["stuff"]); + testConnector.flushAllMessages(); + assert(event.target === array0, '"target" property is set correctly'); + compare(users); +}); + +test("testIteratingArrayContainingTypes", (t) => { + const y = new Y.Doc(); + const arr = y.getOrCreateArray("arr"); + const numItems = 10; + for (let i = 0; i < numItems; i++) { + const map = new Y.Map(); + map.set("value", i); + arr.push([map]); + } + let cnt = 0; + for (const item of arr.iter()) { + assert(item.get("value") === cnt++, "value is correct"); + } + y.destroy(); +}); + +let _uniqueNumber = 0; +const getUniqueNumber = () => _uniqueNumber++; + +const arrayTransactions: Array< + (arg0: Y.Doc, arg1: prng.PRNG, arg2: any) => void +> = [ + function insert(user: Y.Doc, gen: prng.PRNG) { + const yarray = user.getOrCreateArray("array"); + const uniqueNumber = getUniqueNumber(); + const content: number[] = []; + const len = prng.int32(gen, 1, 4); + for (let i = 0; i < len; i++) { + content.push(uniqueNumber); + } + const pos = prng.int32(gen, 0, yarray.length); + const oldContent = yarray.toArray(); + yarray.insert(pos, content); + oldContent.splice(pos, 0, ...content); + deepEqual(yarray.toArray(), oldContent); // we want to make sure that fastSearch markers insert at the correct position + }, + function insertTypeArray(user: Y.Doc, gen: prng.PRNG) { + const yarray = user.getOrCreateArray("array"); + const pos = prng.int32(gen, 0, yarray.length); + yarray.insert(pos, [new Y.Array()]); + const array2 = yarray.get(pos); + array2.insert(0, [1, 2, 3, 4]); + }, + function insertTypeMap(user: Y.Doc, gen: prng.PRNG) { + const yarray = user.getOrCreateArray("array"); + const pos = prng.int32(gen, 0, yarray.length); + yarray.insert(pos, [new Y.Map()]); + const map = yarray.get(pos); + map.set("someprop", 42); + map.set("someprop", 43); + map.set("someprop", 44); + }, + function insertTypeNull(user: Y.Doc, gen: prng.PRNG) { + const yarray = user.getOrCreateArray("array"); + const pos = prng.int32(gen, 0, yarray.length); + yarray.insert(pos, [null]); + }, + function _delete(user: Y.Doc, gen: prng.PRNG) { + const yarray = user.getOrCreateArray("array"); + const length = yarray.length; + if (length > 0) { + let somePos = prng.int32(gen, 0, length - 1); + let delLength = prng.int32(gen, 1, math.min(2, length - somePos)); + if (prng.bool(gen)) { + const type = yarray.get(somePos); + if (type instanceof Y.Array && type.length > 0) { + somePos = prng.int32(gen, 0, type.length - 1); + delLength = prng.int32(gen, 0, math.min(2, type.length - somePos)); + type.delete(somePos, delLength); + } + } else { + const oldContent = yarray.toArray(); + yarray.delete(somePos, delLength); + oldContent.splice(somePos, delLength); + deepEqual(yarray.toArray(), oldContent); + } + } + }, +]; + +test("testRepeatGeneratingYarrayTests6", (t) => { + applyRandomTests(gen, arrayTransactions, 6); +}); + +test("testRepeatGeneratingYarrayTests40", (t) => { + applyRandomTests(gen, arrayTransactions, 40); +}); + +test("testRepeatGeneratingYarrayTests42", (t) => { + applyRandomTests(gen, arrayTransactions, 42); +}); + +test("testRepeatGeneratingYarrayTests43", (t) => { + applyRandomTests(gen, arrayTransactions, 43); +}); + +test("testRepeatGeneratingYarrayTests44", (t) => { + applyRandomTests(gen, arrayTransactions, 44); +}); + +test("testRepeatGeneratingYarrayTests45", (t) => { + applyRandomTests(gen, arrayTransactions, 45); +}); + +test("testRepeatGeneratingYarrayTests46", (t) => { + applyRandomTests(gen, arrayTransactions, 46); +}); + +test("testRepeatGeneratingYarrayTests300", (t) => { + applyRandomTests(gen, arrayTransactions, 300); +}); + +test("testRepeatGeneratingYarrayTests400", (t) => { + applyRandomTests(gen, arrayTransactions, 400); +}); + +test("testRepeatGeneratingYarrayTests500", (t) => { + applyRandomTests(gen, arrayTransactions, 500); +}); + +test("testRepeatGeneratingYarrayTests600", (t) => { + applyRandomTests(gen, arrayTransactions, 600); +}); + +test("testRepeatGeneratingYarrayTests1000", (t) => { + applyRandomTests(gen, arrayTransactions, 1000); +}); + +test("testRepeatGeneratingYarrayTests1800", (t) => { + applyRandomTests(gen, arrayTransactions, 1800); +}); + +test("testRepeatGeneratingYarrayTests3000", (t) => { + if (!production) return; + applyRandomTests(gen, arrayTransactions, 3000); +}); + +test("testRepeatGeneratingYarrayTests5000", (t) => { + if (!production) return; + applyRandomTests(gen, arrayTransactions, 5000); +}); + +test("testRepeatGeneratingYarrayTests30000", (t) => { + if (!production) return; + applyRandomTests(gen, arrayTransactions, 30000); +}); diff --git a/y-octo/src/doc/codec/item.rs b/y-octo/src/doc/codec/item.rs index ce0d31c..91c6171 100644 --- a/y-octo/src/doc/codec/item.rs +++ b/y-octo/src/doc/codec/item.rs @@ -343,6 +343,22 @@ impl Item { Ok(()) } + + pub fn deep_compare(&self, other: &Self) -> bool { + if self.id != other.id + || self.deleted() != other.deleted() + || self.len() != other.len() + || self.left.get().map(|l| l.last_id()) != other.left.get().map(|l| l.last_id()) + || self.right.get().map(|r| r.id) != other.right.get().map(|r| r.id) + || self.origin_left_id != other.origin_left_id + || self.origin_right_id != other.origin_right_id + || self.parent_sub != other.parent_sub + { + return false; + } + + true + } } #[allow(dead_code)] diff --git a/y-octo/src/doc/document.rs b/y-octo/src/doc/document.rs index 274f5ac..72db532 100644 --- a/y-octo/src/doc/document.rs +++ b/y-octo/src/doc/document.rs @@ -203,6 +203,13 @@ impl Doc { } } + pub fn store_compare(&self, other: &Doc) -> bool { + let store = self.store.read().unwrap(); + let other_store = other.store.read().unwrap(); + + store.deep_compare(&other_store) + } + pub fn options(&self) -> &DocOptions { &self.opts } diff --git a/y-octo/src/doc/store.rs b/y-octo/src/doc/store.rs index 9e2088a..2169203 100644 --- a/y-octo/src/doc/store.rs +++ b/y-octo/src/doc/store.rs @@ -941,6 +941,39 @@ impl DocStore { // return the index of processed items idx - pos } + + pub fn deep_compare(&self, other: &Self) -> bool { + if self.items.len() != other.items.len() { + return false; + } + + for (client, structs) in self.items.iter() { + if let Some(other_structs) = other.items.get(client) { + if structs.len() != other_structs.len() { + return false; + } + + for (struct_info, other_struct_info) in structs.iter().zip(other_structs.iter()) { + if struct_info != other_struct_info { + return false; + } + if let (Node::Item(item), Node::Item(other_item)) = (struct_info, other_struct_info) { + if !match (item.get(), other_item.get()) { + (Some(item), Some(other_item)) => item.deep_compare(other_item), + (None, None) => true, + _ => false, + } { + return false; + } + } + } + } else { + return false; + } + } + + true + } } #[cfg(test)] From 412f8443365838c83c76ac51d95d30526f180c83 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Thu, 4 Jul 2024 17:48:44 +0800 Subject: [PATCH 14/75] feat: impl yjs apis --- y-octo-node/src/array.rs | 6 --- y-octo-node/src/map.rs | 93 +++++++++++++++++++++++++++++--- y-octo/src/doc/types/list/mod.rs | 7 ++- y-octo/src/doc/types/value.rs | 2 +- y-octo/src/lib.rs | 6 +-- 5 files changed, 96 insertions(+), 18 deletions(-) diff --git a/y-octo-node/src/array.rs b/y-octo-node/src/array.rs index 52d4129..4c4fb52 100644 --- a/y-octo-node/src/array.rs +++ b/y-octo-node/src/array.rs @@ -11,12 +11,6 @@ pub struct YArray { #[napi] impl YArray { - // patch for Array define in node port - #[napi(constructor)] - pub fn new() -> Self { - unimplemented!() - } - pub(crate) fn inner_new(array: Array) -> Self { Self { array } } diff --git a/y-octo-node/src/map.rs b/y-octo-node/src/map.rs index f18ecf8..9314603 100644 --- a/y-octo-node/src/map.rs +++ b/y-octo-node/src/map.rs @@ -1,4 +1,4 @@ -use napi::{Env, JsFunction, JsObject, ValueType}; +use napi::{bindgen_prelude::Array as JsArray, iterator::Generator, Env, JsFunction, JsObject, ValueType}; use y_octo::{Any, Map, Value}; use super::*; @@ -10,12 +10,6 @@ pub struct YMap { #[napi] impl YMap { - #[allow(clippy::new_without_default)] - #[napi(constructor)] - pub fn new() -> Self { - unimplemented!() - } - pub(crate) fn inner_new(map: Map) -> Self { Self { map } } @@ -106,6 +100,23 @@ impl YMap { Ok(js_object) } + #[napi] + pub fn entries(&self, env: Env) -> YMapEntriesIterator { + YMapEntriesIterator { + entries: self.map.iter().map(|(k, v)| (k.to_owned(), v)).collect(), + env, + current: 0, + } + } + + #[napi] + pub fn keys(&self) -> YMapKeyIterator { + YMapKeyIterator { + keys: self.map.keys().map(ToOwned::to_owned).collect(), + current: 0, + } + } + // TODO(@darkskygit): impl type based observe #[napi] pub fn observe(&mut self, _callback: JsFunction) -> Result<()> { @@ -119,6 +130,74 @@ impl YMap { } } +#[napi(iterator)] +pub struct YMapEntriesIterator { + entries: Vec<(String, Value)>, + env: Env, + current: i64, +} + +#[napi] +impl Generator for YMapEntriesIterator { + type Yield = JsArray; + + type Next = Option; + + type Return = (); + + fn next(&mut self, value: Option) -> Option { + let current = self.current as usize; + if self.entries.len() <= current { + return None; + } + let ret = if let Some((string, value)) = self.entries.get(current) { + let mut js_array = self.env.create_array(2).ok()?; + js_array.set(0, string).ok()?; + js_array + .set(1, get_js_unknown_from_value(self.env, value.clone()).ok()?) + .ok()?; + Some(js_array) + } else { + None + }; + self.current = if let Some(value) = value.and_then(|v| v) { + value + } else { + self.current + 1 + }; + ret + } +} + +#[napi(iterator)] +pub struct YMapKeyIterator { + keys: Vec, + current: i64, +} + +#[napi] +impl Generator for YMapKeyIterator { + type Yield = String; + + type Next = Option; + + type Return = (); + + fn next(&mut self, value: Option) -> Option { + let current = self.current as usize; + if self.keys.len() <= current { + return None; + } + let ret = self.keys.get(current).cloned(); + self.current = if let Some(value) = value.and_then(|v| v) { + value + } else { + self.current + 1 + }; + ret + } +} + #[cfg(test)] mod tests { diff --git a/y-octo/src/doc/types/list/mod.rs b/y-octo/src/doc/types/list/mod.rs index 864fa3d..fcfe7ad 100644 --- a/y-octo/src/doc/types/list/mod.rs +++ b/y-octo/src/doc/types/list/mod.rs @@ -178,7 +178,12 @@ pub(crate) trait ListType: AsInner { return Ok(()); } - if idx >= self.content_len() { + let content_len = self.content_len(); + if content_len == 0 { + return Ok(()); + } + + if idx >= content_len { return Err(JwstCodecError::IndexOutOfBound(idx)); } diff --git a/y-octo/src/doc/types/value.rs b/y-octo/src/doc/types/value.rs index 53a8434..80ec6a1 100644 --- a/y-octo/src/doc/types/value.rs +++ b/y-octo/src/doc/types/value.rs @@ -2,7 +2,7 @@ use std::fmt::Display; use super::*; -#[derive(Debug, PartialEq)] +#[derive(Debug, Clone, PartialEq)] pub enum Value { Any(Any), Doc(Doc), diff --git a/y-octo/src/lib.rs b/y-octo/src/lib.rs index ccced92..9f737eb 100644 --- a/y-octo/src/lib.rs +++ b/y-octo/src/lib.rs @@ -7,9 +7,9 @@ mod sync; pub use codec::*; pub use doc::{ encode_awareness_as_message, encode_update_as_message, merge_updates_v1, Any, Array, Awareness, AwarenessEvent, - Client, ClientMap, Clock, CrdtRead, CrdtReader, CrdtWrite, CrdtWriter, DeleteSet, Doc, DocOptions, - HashMap as AHashMap, HashMapExt, History, HistoryOptions, Id, Map, RawDecoder, RawEncoder, StateVector, - StoreHistory, Text, Update, Value, + Client, ClientMap, Clock, CrdtRead, CrdtReader, CrdtWrite, CrdtWriter, DeleteSet, Doc, DocOptions, EntriesIterator, + HashMap as AHashMap, HashMapExt, History, HistoryOptions, Id, KeysIterator, Map, RawDecoder, RawEncoder, + StateVector, StoreHistory, Text, Update, Value, ValuesIterator, }; pub(crate) use doc::{Content, Item}; use log::{debug, warn}; From ac0f325f8917b8aa045c14a5143daab37bb9ef13 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Fri, 5 Jul 2024 15:27:11 +0800 Subject: [PATCH 15/75] chore: integrate ava tester --- y-octo-node/package.json | 23 +- y-octo-node/tests/yjs/testHelper.ts | 178 +---- y-octo-node/tests/yjs/yarray.spec.ts | 88 ++- yarn.lock | 1097 +++++++++++++++++++++++++- 4 files changed, 1198 insertions(+), 188 deletions(-) diff --git a/y-octo-node/package.json b/y-octo-node/package.json index 719c9cd..3b299d0 100644 --- a/y-octo-node/package.json +++ b/y-octo-node/package.json @@ -23,6 +23,7 @@ "@napi-rs/cli": "^2.16.2", "@types/node": "^18.17.5", "@types/prompts": "^2.4.4", + "ava": "^6.1.3", "c8": "^8.0.1", "prompts": "^2.4.2", "tsx": "^4.16.2", @@ -38,7 +39,7 @@ "build": "napi build --platform --release --no-const-enum", "build:debug": "napi build --platform --no-const-enum", "universal": "napi universal", - "test": "NODE_NO_WARNINGS=1 yarn exec tsx --no-warnings ./scripts/run-test.mts all", + "test": "ava --concurrency 1 --serial", "test:watch": "yarn exec tsx ./scripts/run-test.mts all --watch", "test:coverage": "NODE_OPTIONS=\"--import tsx\" c8 node ./scripts/run-test.mts all", "version": "napi version" @@ -57,6 +58,26 @@ "DEBUG": "napi:*" } }, + "ava": { + "timeout": "1m", + "extensions": { + "ts": "module" + }, + "workerThreads": false, + "nodeArguments": [ + "--trace-sigint", + "--import", + "tsx", + "--es-module-specifier-resolution=node" + ], + "files": [ + "tests/**/*.spec.ts" + ], + "environmentVariables": { + "TS_NODE_PROJECT": "./tests/tsconfig.json", + "NODE_ENV": "test" + } + }, "c8": { "reporter": [ "text", diff --git a/y-octo-node/tests/yjs/testHelper.ts b/y-octo-node/tests/yjs/testHelper.ts index e2266c8..70c190d 100644 --- a/y-octo-node/tests/yjs/testHelper.ts +++ b/y-octo-node/tests/yjs/testHelper.ts @@ -299,11 +299,9 @@ export const init = ( * 3. get type content * 4. disconnect & reconnect all (so gc is propagated) * 5. compare os, ds, ss - * - * @param {Array} users */ -export const compare = (users: any[]) => { - users.forEach((u: { connect: () => any }) => u.connect()); +export const compare = (users: TestYOctoInstance[]) => { + users.forEach((u) => u.connect()); while (users[0].tc.flushAllMessages()) {} // eslint-disable-line // For each document, merge all received document updates with Y.mergeUpdates and create a new document which will be added to the list of "users" // This ensures that mergeUpdates works correctly @@ -312,25 +310,11 @@ export const compare = (users: any[]) => { enc.applyUpdate(ydoc, enc.mergeUpdates(user.updates)); return ydoc; }); - users.push(.../** @type {any} */ mergedDocs); - const userArrayValues = users.map( - (u: { - getArray: (arg0: string) => { - (): any; - new (): any; - toJSON: { (): any; new (): any }; - }; - }) => u.getArray("array").toJSON(), - ); - const userMapValues = users.map( - (u: { - getMap: (arg0: string) => { - (): any; - new (): any; - toJSON: { (): any; new (): any }; - }; - }) => u.getMap("map").toJSON(), + users.push(...mergedDocs); + const userArrayValues = users.map((u) => + u.getOrCreateArray("array").toJSON(), ); + const userMapValues = users.map((u) => u.getOrCreateMap("map").toJson()); // const userXmlValues = users.map( // (u: { // get: ( @@ -339,149 +323,73 @@ export const compare = (users: any[]) => { // ) => { (): any; new (): any; toString: { (): any; new (): any } }; // }) => u.get("xml", Y.XmlElement).toString(), // ); - const userTextValues = users.map( - (u: { - getText: (arg0: string) => { - (): any; - new (): any; - toDelta: { (): any; new (): any }; - }; - }) => u.getText("text").toDelta(), - ); - for (const u of users) { - t.assert(u.store.pendingDs === null); - t.assert(u.store.pendingStructs === null); - } + // const userTextValues = users.map((u) => u.getOrCreateText("text").toDelta()); + // for (const u of users) { + // t.assert(u.store.pendingDs === null); + // t.assert(u.store.pendingStructs === null); + // } // Test Array iterator t.compare( - users[0].getArray("array").toArray(), - Array.from(users[0].getArray("array")), + users[0].getOrCreateArray("array").toArray(), + Array.from(users[0].getOrCreateArray("array").iter()), ); // Test Map iterator - const ymapkeys: any[] = Array.from(users[0].getMap("map").keys()); + const ymapkeys: any[] = Array.from(users[0].getOrCreateMap("map").keys()); t.assert(ymapkeys.length === Object.keys(userMapValues[0]).length); ymapkeys.forEach((key) => t.assert(object.hasProperty(userMapValues[0], key)), ); const mapRes: Record = {}; - for (const [k, v] of users[0].getMap("map")) { + for (const [k, v] of users[0].getOrCreateMap("map").entries()) { mapRes[k] = Y.isAbstractType(v) ? v.toJSON() : v; } t.compare(userMapValues[0], mapRes); // Compare all users for (let i = 0; i < users.length - 1; i++) { - t.compare(userArrayValues[i].length, users[i].getArray("array").length); + t.compare( + userArrayValues[i].length, + users[i].getOrCreateArray("array").length, + ); 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: { insert: any }) => - typeof a.insert === "string" ? a.insert : " ", - ) - .join("").length, - users[i].getText("text").length, - ); - t.compare( - userTextValues[i], - userTextValues[i + 1], - "", - (_constructor, a, b) => { - if (Y.isAbstractType(a)) { - t.compare(a.toJSON(), b.toJSON()); - } else if (a !== b) { - t.fail("Deltas dont match"); - } - return true; - }, - ); + // t.compare( + // userTextValues[i] + // .map( + // /** @param {any} a */ (a: { insert: any }) => + // typeof a.insert === "string" ? a.insert : " ", + // ) + // .join("").length, + // users[i].getOrCreateText("text").length, + // ); + // t.compare( + // userTextValues[i], + // userTextValues[i + 1], + // "", + // (_constructor, a, b) => { + // if (Y.isAbstractType(a)) { + // t.compare(a.toJSON(), b.toJSON()); + // } else if (a !== b) { + // t.fail("Deltas dont match"); + // } + // return true; + // }, + // ); t.compare(Y.encodeStateVector(users[i]), Y.encodeStateVector(users[i + 1])); Y.equalDeleteSets( Y.createDeleteSetFromStructStore(users[i].store), Y.createDeleteSetFromStructStore(users[i + 1].store), ); - compareStructStores(users[i].store, users[i + 1].store); + Y.compareStructStores(users[i].store, users[i + 1].store); t.compare( Y.encodeSnapshot(Y.snapshot(users[i])), Y.encodeSnapshot(Y.snapshot(users[i + 1])), ); } - users.map((u: { destroy: () => any }) => u.destroy()); -}; - -export const compareItemIDs = ( - a: { id: any } | null, - b: { id: any } | null, -): boolean => a === b || (a !== null && b != null && Y.compareIDs(a.id, b.id)); - -/** - * @param {import('../src/internals.js').StructStore} ss1 - * @param {import('../src/internals.js').StructStore} ss2 - */ -export const compareStructStores = ( - ss1: { clients: { size: any } }, - ss2: { clients: { size: any; get: (arg0: any) => any } }, -) => { - t.assert(ss1.clients.size === ss2.clients.size); - for (const [client, structs1] of ss1.clients) { - const structs2 = - /** @type {Array} */ ss2.clients.get(client); - t.assert(structs2 !== undefined && structs1.length === structs2.length); - for (let i = 0; i < structs1.length; i++) { - const s1 = structs1[i]; - const s2 = structs2[i]; - // checks for abstract struct - if ( - s1.constructor !== s2.constructor || - !Y.compareIDs(s1.id, s2.id) || - s1.deleted !== s2.deleted || - // @ts-ignore - s1.length !== s2.length - ) { - t.fail("Structs dont match"); - } - if (s1 instanceof Y.Item) { - if ( - !(s2 instanceof Y.Item) || - !( - (s1.left === null && s2.left === null) || - (s1.left !== null && - s2.left !== null && - Y.compareIDs(s1.left.lastId, s2.left.lastId)) - ) || - !compareItemIDs(s1.right, s2.right) || - !Y.compareIDs(s1.origin, s2.origin) || - !Y.compareIDs(s1.rightOrigin, s2.rightOrigin) || - s1.parentSub !== s2.parentSub - ) { - return t.fail("Items dont match"); - } - // make sure that items are connected correctly - t.assert(s1.left === null || s1.left.right === s1); - t.assert(s1.right === null || s1.right.left === s1); - t.assert(s2.left === null || s2.left.right === s2); - t.assert(s2.right === null || s2.right.left === s2); - } - } - } + // users.map((u) => u.destroy()); }; -/** - * @template T - * @callback InitTestObjectCallback - * @param {TestYInstance} y - * @return {T} - */ - -/** - * @template T - * @param {t.TestCase} tc - * @param {Array} mods - * @param {number} iterations - * @param {InitTestObjectCallback} [initTestObject] - */ export const applyRandomTests = ( gen: prng.PRNG, mods: unknown[], diff --git a/y-octo-node/tests/yjs/yarray.spec.ts b/y-octo-node/tests/yjs/yarray.spec.ts index 28fc8fa..f9b90fd 100644 --- a/y-octo-node/tests/yjs/yarray.spec.ts +++ b/y-octo-node/tests/yjs/yarray.spec.ts @@ -36,7 +36,7 @@ test("testSlice", (t) => { t.deepEqual(arr.slice(0, 2), [0, 1]); }); -test("testArrayFrom", (t) => { +test.skip("testArrayFrom", (t) => { const doc1 = new Y.Doc(); const db1 = doc1.getOrCreateMap("root"); const nestedArray1 = Y.Array.from([0, 1, 2]); @@ -74,7 +74,7 @@ test("testLengthIssue", (t) => { /** * Debugging yjs#314 */ -test("testLengthIssue2", (t) => { +test.skip("testLengthIssue2", (t) => { const doc = new Y.Doc(); const next = doc.createArray(); doc.transact(() => { @@ -99,7 +99,7 @@ test("testLengthIssue2", (t) => { }); doc.transact(() => { doc.transact(() => { - t.notThrows(() => { + t.throws(() => { next.insert(5, ["rectangle2"]); }); next.insert(4, ["rectangle2"]); @@ -112,7 +112,7 @@ test("testLengthIssue2", (t) => { console.log(next.toArray()); }); -test("testDeleteInsert", (t) => { +test.skip("testDeleteInsert", (t) => { const { users, array0 } = init(gen, { users: 2 }); assert(array0); array0.delete(0, 0); @@ -126,7 +126,8 @@ test("testDeleteInsert", (t) => { compare(users); }); -test("testInsertThreeElementsTryRegetProperty", (t) => { +// TODO: impl sync protocol encode in rust +test.skip("testInsertThreeElementsTryRegetProperty", (t) => { const { testConnector, users, array0, array1 } = init(gen, { users: 2 }); assert(array0); assert(array1); @@ -137,7 +138,7 @@ test("testInsertThreeElementsTryRegetProperty", (t) => { compare(users); }); -test("testConcurrentInsertWithThreeConflicts", (t) => { +test.skip("testConcurrentInsertWithThreeConflicts", (t) => { const { users, array0, array1, array2 } = init(gen, { users: 3 }); assert(array0); assert(array1); @@ -148,7 +149,7 @@ test("testConcurrentInsertWithThreeConflicts", (t) => { compare(users); }); -test("testConcurrentInsertDeleteWithThreeConflicts", (t) => { +test.skip("testConcurrentInsertDeleteWithThreeConflicts", (t) => { const { testConnector, users, array0, array1, array2 } = init(gen, { users: 3, }); @@ -164,7 +165,7 @@ test("testConcurrentInsertDeleteWithThreeConflicts", (t) => { compare(users); }); -test("testInsertionsInLateSync", (t) => { +test.skip("testInsertionsInLateSync", (t) => { const { testConnector, users, array0, array1, array2 } = init(gen, { users: 3, }); @@ -184,7 +185,7 @@ test("testInsertionsInLateSync", (t) => { compare(users); }); -test("testDisconnectReallyPreventsSendingMessages", (t) => { +test.skip("testDisconnectReallyPreventsSendingMessages", (t) => { const { testConnector, users, array0, array1 } = init(gen, { users: 3 }); assert(array0); assert(array1); @@ -201,7 +202,7 @@ test("testDisconnectReallyPreventsSendingMessages", (t) => { compare(users); }); -test("testDeletionsInLateSync", (t) => { +test.skip("testDeletionsInLateSync", (t) => { const { testConnector, users, array0, array1 } = init(gen, { users: 2 }); assert(array0); assert(array1); @@ -214,7 +215,7 @@ test("testDeletionsInLateSync", (t) => { compare(users); }); -test("testInsertThenMergeDeleteOnSync", (t) => { +test.skip("testInsertThenMergeDeleteOnSync", (t) => { const { testConnector, users, array0, array1 } = init(gen, { users: 2 }); assert(array0); assert(array1); @@ -226,7 +227,7 @@ test("testInsertThenMergeDeleteOnSync", (t) => { compare(users); }); -test("testInsertAndDeleteEvents", (t) => { +test.skip("testInsertAndDeleteEvents", (t) => { const { array0, users } = init(gen, { users: 2 }); assert(array0); let event: Record | null = null; @@ -245,7 +246,7 @@ test("testInsertAndDeleteEvents", (t) => { compare(users); }); -test("testNestedObserverEvents", (t) => { +test.skip("testNestedObserverEvents", (t) => { const { array0, users } = init(gen, { users: 2 }); assert(array0); const vals: number[] = []; @@ -266,7 +267,7 @@ test("testNestedObserverEvents", (t) => { compare(users); }); -test("testInsertAndDeleteEventsForTypes", (t) => { +test.skip("testInsertAndDeleteEventsForTypes", (t) => { const { array0, users } = init(gen, { users: 2 }); assert(array0); let event: Record | null = null; @@ -289,7 +290,7 @@ test("testInsertAndDeleteEventsForTypes", (t) => { * and item 1 changed then the array-add event should fire first so that the change event * path is correct. A array binding might lead to an inconsistent state otherwise. */ -test("testObserveDeepEventOrder", (t) => { +test.skip("testObserveDeepEventOrder", (t) => { const { array0, users } = init(gen, { users: 2 }); assert(array0); let events: any[] = []; @@ -312,7 +313,7 @@ test("testObserveDeepEventOrder", (t) => { /** * Correct index when computing event.path in observeDeep - https://github.com/yjs/yjs/issues/457 */ -test("testObservedeepIndexes", (t) => { +test.skip("testObservedeepIndexes", (t) => { const doc = new Y.Doc(); const map = doc.createMap(); // Create a field with the array as value @@ -329,7 +330,7 @@ test("testObservedeepIndexes", (t) => { t.deepEqual(eventPath, ["my-array", 3]); }); -test("testChangeEvent", (t) => { +test.skip("testChangeEvent", (t) => { const { array0, users } = init(gen, { users: 2 }); assert(array0); let changes: any = null; @@ -357,7 +358,7 @@ test("testChangeEvent", (t) => { compare(users); }); -test("testInsertAndDeleteEventsForTypes2", (t) => { +test.skip("testInsertAndDeleteEventsForTypes2", (t) => { const { array0, users } = init(gen, { users: 2 }); assert(array0); const events: Record[] = []; @@ -378,7 +379,7 @@ test("testInsertAndDeleteEventsForTypes2", (t) => { /** * This issue has been reported here https://github.com/yjs/yjs/issues/155 */ -test("testNewChildDoesNotEmitEventInTransaction", (t) => { +test.skip("testNewChildDoesNotEmitEventInTransaction", (t) => { const { array0, users } = init(gen, { users: 2 }); assert(array0); let fired = false; @@ -393,7 +394,7 @@ test("testNewChildDoesNotEmitEventInTransaction", (t) => { assert(!fired, "Event does not trigger"); }); -test("testGarbageCollector", (t) => { +test.skip("testGarbageCollector", (t) => { const { testConnector, users, array0 } = init(gen, { users: 3 }); assert(array0); array0.insert(0, ["x", "y", "z"]); @@ -405,7 +406,7 @@ test("testGarbageCollector", (t) => { compare(users); }); -test("testEventTargetIsSetCorrectlyOnLocal", (t) => { +test.skip("testEventTargetIsSetCorrectlyOnLocal", (t) => { const { array0, users } = init(gen, { users: 3 }); assert(array0); let event: any; @@ -417,7 +418,7 @@ test("testEventTargetIsSetCorrectlyOnLocal", (t) => { compare(users); }); -test("testEventTargetIsSetCorrectlyOnRemote", (t) => { +test.skip("testEventTargetIsSetCorrectlyOnRemote", (t) => { const { testConnector, array0, array1, users } = init(gen, { users: 3 }); assert(array0); assert(array1); @@ -436,15 +437,16 @@ test("testIteratingArrayContainingTypes", (t) => { const arr = y.getOrCreateArray("arr"); const numItems = 10; for (let i = 0; i < numItems; i++) { - const map = new Y.Map(); + const map = y.createMap(); map.set("value", i); arr.push([map]); } + t.is(arr.length, numItems, "array length mot correct"); let cnt = 0; for (const item of arr.iter()) { - assert(item.get("value") === cnt++, "value is correct"); + t.is(item.get("value"), cnt++, "value is correct"); } - y.destroy(); + // y.destroy(); }); let _uniqueNumber = 0; @@ -470,14 +472,14 @@ const arrayTransactions: Array< function insertTypeArray(user: Y.Doc, gen: prng.PRNG) { const yarray = user.getOrCreateArray("array"); const pos = prng.int32(gen, 0, yarray.length); - yarray.insert(pos, [new Y.Array()]); + yarray.insert(pos, [user.createArray()]); const array2 = yarray.get(pos); array2.insert(0, [1, 2, 3, 4]); }, function insertTypeMap(user: Y.Doc, gen: prng.PRNG) { const yarray = user.getOrCreateArray("array"); const pos = prng.int32(gen, 0, yarray.length); - yarray.insert(pos, [new Y.Map()]); + yarray.insert(pos, [user.createMap()]); const map = yarray.get(pos); map.set("someprop", 42); map.set("someprop", 43); @@ -511,69 +513,69 @@ const arrayTransactions: Array< }, ]; -test("testRepeatGeneratingYarrayTests6", (t) => { +test.skip("testRepeatGeneratingYarrayTests6", (t) => { applyRandomTests(gen, arrayTransactions, 6); }); -test("testRepeatGeneratingYarrayTests40", (t) => { +test.skip("testRepeatGeneratingYarrayTests40", (t) => { applyRandomTests(gen, arrayTransactions, 40); }); -test("testRepeatGeneratingYarrayTests42", (t) => { +test.skip("testRepeatGeneratingYarrayTests42", (t) => { applyRandomTests(gen, arrayTransactions, 42); }); -test("testRepeatGeneratingYarrayTests43", (t) => { +test.skip("testRepeatGeneratingYarrayTests43", (t) => { applyRandomTests(gen, arrayTransactions, 43); }); -test("testRepeatGeneratingYarrayTests44", (t) => { +test.skip("testRepeatGeneratingYarrayTests44", (t) => { applyRandomTests(gen, arrayTransactions, 44); }); -test("testRepeatGeneratingYarrayTests45", (t) => { +test.skip("testRepeatGeneratingYarrayTests45", (t) => { applyRandomTests(gen, arrayTransactions, 45); }); -test("testRepeatGeneratingYarrayTests46", (t) => { +test.skip("testRepeatGeneratingYarrayTests46", (t) => { applyRandomTests(gen, arrayTransactions, 46); }); -test("testRepeatGeneratingYarrayTests300", (t) => { +test.skip("testRepeatGeneratingYarrayTests300", (t) => { applyRandomTests(gen, arrayTransactions, 300); }); -test("testRepeatGeneratingYarrayTests400", (t) => { +test.skip("testRepeatGeneratingYarrayTests400", (t) => { applyRandomTests(gen, arrayTransactions, 400); }); -test("testRepeatGeneratingYarrayTests500", (t) => { +test.skip("testRepeatGeneratingYarrayTests500", (t) => { applyRandomTests(gen, arrayTransactions, 500); }); -test("testRepeatGeneratingYarrayTests600", (t) => { +test.skip("testRepeatGeneratingYarrayTests600", (t) => { applyRandomTests(gen, arrayTransactions, 600); }); -test("testRepeatGeneratingYarrayTests1000", (t) => { +test.skip("testRepeatGeneratingYarrayTests1000", (t) => { applyRandomTests(gen, arrayTransactions, 1000); }); -test("testRepeatGeneratingYarrayTests1800", (t) => { +test.skip("testRepeatGeneratingYarrayTests1800", (t) => { applyRandomTests(gen, arrayTransactions, 1800); }); -test("testRepeatGeneratingYarrayTests3000", (t) => { +test.skip("testRepeatGeneratingYarrayTests3000", (t) => { if (!production) return; applyRandomTests(gen, arrayTransactions, 3000); }); -test("testRepeatGeneratingYarrayTests5000", (t) => { +test.skip("testRepeatGeneratingYarrayTests5000", (t) => { if (!production) return; applyRandomTests(gen, arrayTransactions, 5000); }); -test("testRepeatGeneratingYarrayTests30000", (t) => { +test.skip("testRepeatGeneratingYarrayTests30000", (t) => { if (!production) return; applyRandomTests(gen, arrayTransactions, 30000); }); diff --git a/yarn.lock b/yarn.lock index 304786e..b6ee53e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -218,6 +218,25 @@ __metadata: languageName: node linkType: hard +"@mapbox/node-pre-gyp@npm:^1.0.5": + version: 1.0.11 + resolution: "@mapbox/node-pre-gyp@npm:1.0.11" + dependencies: + detect-libc: ^2.0.0 + https-proxy-agent: ^5.0.0 + make-dir: ^3.1.0 + node-fetch: ^2.6.7 + nopt: ^5.0.0 + npmlog: ^5.0.1 + rimraf: ^3.0.2 + semver: ^7.3.5 + tar: ^6.1.11 + bin: + node-pre-gyp: bin/node-pre-gyp + checksum: b848f6abc531a11961d780db813cc510ca5a5b6bf3184d72134089c6875a91c44d571ba6c1879470020803f7803609e7b2e6e429651c026fe202facd11d444b8 + languageName: node + linkType: hard + "@napi-rs/cli@npm:^2.16.2": version: 2.16.3 resolution: "@napi-rs/cli@npm:2.16.3" @@ -227,6 +246,33 @@ __metadata: languageName: node linkType: hard +"@nodelib/fs.scandir@npm:2.1.5": + version: 2.1.5 + resolution: "@nodelib/fs.scandir@npm:2.1.5" + dependencies: + "@nodelib/fs.stat": 2.0.5 + run-parallel: ^1.1.9 + checksum: a970d595bd23c66c880e0ef1817791432dbb7acbb8d44b7e7d0e7a22f4521260d4a83f7f9fd61d44fda4610105577f8f58a60718105fb38352baed612fd79e59 + languageName: node + linkType: hard + +"@nodelib/fs.stat@npm:2.0.5, @nodelib/fs.stat@npm:^2.0.2": + version: 2.0.5 + resolution: "@nodelib/fs.stat@npm:2.0.5" + checksum: 012480b5ca9d97bff9261571dbbec7bbc6033f69cc92908bc1ecfad0792361a5a1994bc48674b9ef76419d056a03efadfce5a6cf6dbc0a36559571a7a483f6f0 + languageName: node + linkType: hard + +"@nodelib/fs.walk@npm:^1.2.3": + version: 1.2.8 + resolution: "@nodelib/fs.walk@npm:1.2.8" + dependencies: + "@nodelib/fs.scandir": 2.1.5 + fastq: ^1.6.0 + checksum: 190c643f156d8f8f277bf2a6078af1ffde1fd43f498f187c2db24d35b4b4b5785c02c7dc52e356497b9a1b65b13edc996de08de0b961c32844364da02986dc53 + languageName: node + linkType: hard + "@nolyfill/shared@npm:1.0.20": version: 1.0.20 resolution: "@nolyfill/shared@npm:1.0.20" @@ -263,6 +309,23 @@ __metadata: languageName: node linkType: hard +"@rollup/pluginutils@npm:^4.0.0": + version: 4.2.1 + resolution: "@rollup/pluginutils@npm:4.2.1" + dependencies: + estree-walker: ^2.0.1 + picomatch: ^2.2.2 + checksum: 6bc41f22b1a0f1efec3043899e4d3b6b1497b3dea4d94292d8f83b4cf07a1073ecbaedd562a22d11913ff7659f459677b01b09e9598a98936e746780ecc93a12 + languageName: node + linkType: hard + +"@sindresorhus/merge-streams@npm:^2.1.0": + version: 2.3.0 + resolution: "@sindresorhus/merge-streams@npm:2.3.0" + checksum: e989d53dee68d7e49b4ac02ae49178d561c461144cea83f66fa91ff012d981ad0ad2340cbd13f2fdb57989197f5c987ca22a74eb56478626f04e79df84291159 + languageName: node + linkType: hard + "@taplo/cli@npm:^0.5.2": version: 0.5.2 resolution: "@taplo/cli@npm:0.5.2" @@ -303,6 +366,28 @@ __metadata: languageName: node linkType: hard +"@vercel/nft@npm:^0.26.2": + version: 0.26.5 + resolution: "@vercel/nft@npm:0.26.5" + dependencies: + "@mapbox/node-pre-gyp": ^1.0.5 + "@rollup/pluginutils": ^4.0.0 + acorn: ^8.6.0 + acorn-import-attributes: ^1.9.2 + async-sema: ^3.1.1 + bindings: ^1.4.0 + estree-walker: 2.0.2 + glob: ^7.1.3 + graceful-fs: ^4.2.9 + micromatch: ^4.0.2 + node-gyp-build: ^4.2.2 + resolve-from: ^5.0.0 + bin: + nft: out/cli.js + checksum: 0856c2a7d1c3e7f2bb624891570309a1ee06510338d45a7bc5517486f8c789d897e0783e1ee34782c905865b969f808bee53a145c748673b1a4bf9dd346f0788 + languageName: node + linkType: hard + "@y-octo/cli@workspace:.": version: 0.0.0-use.local resolution: "@y-octo/cli@workspace:." @@ -322,6 +407,7 @@ __metadata: "@napi-rs/cli": ^2.16.2 "@types/node": ^18.17.5 "@types/prompts": ^2.4.4 + ava: ^6.1.3 c8: ^8.0.1 prompts: ^2.4.2 tsx: ^4.16.2 @@ -331,6 +417,13 @@ __metadata: languageName: unknown linkType: soft +"abbrev@npm:1": + version: 1.1.1 + resolution: "abbrev@npm:1.1.1" + checksum: a4a97ec07d7ea112c517036882b2ac22f3109b7b19077dc656316d07d308438aac28e4d9746dc4d84bf6b1e75b4a7b0a5f3cb30592419f128ca9a8cee3bcfa17 + languageName: node + linkType: hard + "abbrev@npm:^2.0.0": version: 2.0.0 resolution: "abbrev@npm:2.0.0" @@ -338,6 +431,42 @@ __metadata: languageName: node linkType: hard +"acorn-import-attributes@npm:^1.9.2": + version: 1.9.5 + resolution: "acorn-import-attributes@npm:1.9.5" + peerDependencies: + acorn: ^8 + checksum: 1c0c49b6a244503964ae46ae850baccf306e84caf99bc2010ed6103c69a423987b07b520a6c619f075d215388bd4923eccac995886a54309eda049ab78a4be95 + languageName: node + linkType: hard + +"acorn-walk@npm:^8.3.2": + version: 8.3.3 + resolution: "acorn-walk@npm:8.3.3" + dependencies: + acorn: ^8.11.0 + checksum: 0f09d351fc30b69b2b9982bf33dc30f3d35a34e030e5f1ed3c49fc4e3814a192bf3101e4c30912a0595410f5e91bb70ddba011ea73398b3ecbfe41c7334c6dd0 + languageName: node + linkType: hard + +"acorn@npm:^8.11.0, acorn@npm:^8.11.3, acorn@npm:^8.6.0": + version: 8.12.1 + resolution: "acorn@npm:8.12.1" + bin: + acorn: bin/acorn + checksum: 677880034aee5bdf7434cc2d25b641d7bedb0b5ef47868a78dadabedccf58e1c5457526d9d8249cd253f2df087e081c3fe7d903b448d8e19e5131a3065b83c07 + languageName: node + linkType: hard + +"agent-base@npm:6": + version: 6.0.2 + resolution: "agent-base@npm:6.0.2" + dependencies: + debug: 4 + checksum: f52b6872cc96fd5f622071b71ef200e01c7c4c454ee68bc9accca90c98cfb39f2810e3e9aa330435835eedc8c23f4f8a15267f67c6e245d2b33757575bdac49d + languageName: node + linkType: hard + "agent-base@npm:^7.0.2, agent-base@npm:^7.1.0, agent-base@npm:^7.1.1": version: 7.1.1 resolution: "agent-base@npm:7.1.1" @@ -398,13 +527,122 @@ __metadata: languageName: node linkType: hard -"ansi-styles@npm:^6.0.0, ansi-styles@npm:^6.1.0": +"ansi-styles@npm:^6.0.0, ansi-styles@npm:^6.1.0, ansi-styles@npm:^6.2.1": version: 6.2.1 resolution: "ansi-styles@npm:6.2.1" checksum: ef940f2f0ced1a6347398da88a91da7930c33ecac3c77b72c5905f8b8fe402c52e6fde304ff5347f616e27a742da3f1dc76de98f6866c69251ad0b07a66776d9 languageName: node linkType: hard +"aproba@npm:^1.0.3 || ^2.0.0": + version: 2.0.0 + resolution: "aproba@npm:2.0.0" + checksum: 5615cadcfb45289eea63f8afd064ab656006361020e1735112e346593856f87435e02d8dcc7ff0d11928bc7d425f27bc7c2a84f6c0b35ab0ff659c814c138a24 + languageName: node + linkType: hard + +"are-we-there-yet@npm:^2.0.0": + version: 2.0.0 + resolution: "are-we-there-yet@npm:2.0.0" + dependencies: + delegates: ^1.0.0 + readable-stream: ^3.6.0 + checksum: 6c80b4fd04ecee6ba6e737e0b72a4b41bdc64b7d279edfc998678567ff583c8df27e27523bc789f2c99be603ffa9eaa612803da1d886962d2086e7ff6fa90c7c + languageName: node + linkType: hard + +"argparse@npm:^1.0.7": + version: 1.0.10 + resolution: "argparse@npm:1.0.10" + dependencies: + sprintf-js: ~1.0.2 + checksum: 7ca6e45583a28de7258e39e13d81e925cfa25d7d4aacbf806a382d3c02fcb13403a07fb8aeef949f10a7cfe4a62da0e2e807b348a5980554cc28ee573ef95945 + languageName: node + linkType: hard + +"array-find-index@npm:^1.0.1": + version: 1.0.2 + resolution: "array-find-index@npm:1.0.2" + checksum: aac128bf369e1ac6c06ff0bb330788371c0e256f71279fb92d745e26fb4b9db8920e485b4ec25e841c93146bf71a34dcdbcefa115e7e0f96927a214d237b7081 + languageName: node + linkType: hard + +"arrgv@npm:^1.0.2": + version: 1.0.2 + resolution: "arrgv@npm:1.0.2" + checksum: 470bbb406ea3b34810dd8b03c0b33282617a42d9fce0ab45d58596efefd042fc548eda49161fa8e3f607cbe9df90e7a67003a09043ab9081eff70f97c63dd0e2 + languageName: node + linkType: hard + +"arrify@npm:^3.0.0": + version: 3.0.0 + resolution: "arrify@npm:3.0.0" + checksum: d6c6f3dad9571234f320e130d57fddb2cc283c87f2ac7df6c7005dffc5161b7bb9376f4be655ed257050330336e84afc4f3020d77696ad231ff580a94ae5aba6 + languageName: node + linkType: hard + +"async-sema@npm:^3.1.1": + version: 3.1.1 + resolution: "async-sema@npm:3.1.1" + checksum: 07b8c51f6cab107417ecdd8126b7a9fe5a75151b7f69fdd420dcc8ee08f9e37c473a217247e894b56e999b088b32e902dbe41637e4e9b594d3f8dfcdddfadc5e + languageName: node + linkType: hard + +"ava@npm:^6.1.3": + version: 6.1.3 + resolution: "ava@npm:6.1.3" + dependencies: + "@vercel/nft": ^0.26.2 + acorn: ^8.11.3 + acorn-walk: ^8.3.2 + ansi-styles: ^6.2.1 + arrgv: ^1.0.2 + arrify: ^3.0.0 + callsites: ^4.1.0 + cbor: ^9.0.1 + chalk: ^5.3.0 + chunkd: ^2.0.1 + ci-info: ^4.0.0 + ci-parallel-vars: ^1.0.1 + cli-truncate: ^4.0.0 + code-excerpt: ^4.0.0 + common-path-prefix: ^3.0.0 + concordance: ^5.0.4 + currently-unhandled: ^0.4.1 + debug: ^4.3.4 + emittery: ^1.0.1 + figures: ^6.0.1 + globby: ^14.0.0 + ignore-by-default: ^2.1.0 + indent-string: ^5.0.0 + is-plain-object: ^5.0.0 + is-promise: ^4.0.0 + matcher: ^5.0.0 + memoize: ^10.0.0 + ms: ^2.1.3 + p-map: ^7.0.1 + package-config: ^5.0.0 + picomatch: ^3.0.1 + plur: ^5.1.0 + pretty-ms: ^9.0.0 + resolve-cwd: ^3.0.0 + stack-utils: ^2.0.6 + strip-ansi: ^7.1.0 + supertap: ^3.0.1 + temp-dir: ^3.0.0 + write-file-atomic: ^5.0.1 + yargs: ^17.7.2 + peerDependencies: + "@ava/typescript": "*" + peerDependenciesMeta: + "@ava/typescript": + optional: true + bin: + ava: entrypoints/cli.mjs + checksum: a445de7b9b7ae73bd7f5b15114a2e6fdfc5675d4b2d65fef9c63b3ce4f1dc2a25e4827209c7eaf2e54490d3d7d8bc27eea3a8cfd08807a9d7fcf8ef035d1e143 + languageName: node + linkType: hard + "balanced-match@npm:^1.0.0": version: 1.0.2 resolution: "balanced-match@npm:1.0.2" @@ -412,6 +650,22 @@ __metadata: languageName: node linkType: hard +"bindings@npm:^1.4.0": + version: 1.5.0 + resolution: "bindings@npm:1.5.0" + dependencies: + file-uri-to-path: 1.0.0 + checksum: 65b6b48095717c2e6105a021a7da4ea435aa8d3d3cd085cb9e85bcb6e5773cf318c4745c3f7c504412855940b585bdf9b918236612a1c7a7942491de176f1ae7 + languageName: node + linkType: hard + +"blueimp-md5@npm:^2.10.0": + version: 2.19.0 + resolution: "blueimp-md5@npm:2.19.0" + checksum: 28095dcbd2c67152a2938006e8d7c74c3406ba6556071298f872505432feb2c13241b0476644160ee0a5220383ba94cb8ccdac0053b51f68d168728f9c382530 + languageName: node + linkType: hard + "brace-expansion@npm:^1.1.7": version: 1.1.11 resolution: "brace-expansion@npm:1.1.11" @@ -440,6 +694,15 @@ __metadata: languageName: node linkType: hard +"braces@npm:^3.0.3": + version: 3.0.3 + resolution: "braces@npm:3.0.3" + dependencies: + fill-range: ^7.1.1 + checksum: b95aa0b3bd909f6cd1720ffcf031aeaf46154dd88b4da01f9a1d3f7ea866a79eba76a6d01cbc3c422b2ee5cdc39a4f02491058d5df0d7bf6e6a162a832df1f69 + languageName: node + linkType: hard + "c8@npm:^8.0.1": version: 8.0.1 resolution: "c8@npm:8.0.1" @@ -482,7 +745,23 @@ __metadata: languageName: node linkType: hard -"chalk@npm:5.3.0": +"callsites@npm:^4.1.0": + version: 4.2.0 + resolution: "callsites@npm:4.2.0" + checksum: 9a740675712076a38208967d7f80b525c9c7f4524c2af5d3936c5e278a601af0423a07e91f79679fec0546f3a52514d56969c6fe65f84d794e64a36b1f5eda8a + languageName: node + linkType: hard + +"cbor@npm:^9.0.1": + version: 9.0.2 + resolution: "cbor@npm:9.0.2" + dependencies: + nofilter: ^3.1.0 + checksum: 925edae7bf964be5a26dba1b7ba6311ac12b6a66234dc958958997a0576cdc740632dc19852a5b84d8a75101936bea1fe122dc22539d6e11f4539c731853ba2e + languageName: node + linkType: hard + +"chalk@npm:5.3.0, chalk@npm:^5.3.0": version: 5.3.0 resolution: "chalk@npm:5.3.0" checksum: 623922e077b7d1e9dedaea6f8b9e9352921f8ae3afe739132e0e00c275971bdd331268183b2628cf4ab1727c45ea1f28d7e24ac23ce1db1eb653c414ca8a5a80 @@ -507,6 +786,27 @@ __metadata: languageName: node linkType: hard +"chunkd@npm:^2.0.1": + version: 2.0.1 + resolution: "chunkd@npm:2.0.1" + checksum: bab8cc08c752a3648984385dc6f61d751e89dbeef648d22a3b661e1d470eaa0f5182f0b4303710f13ae83d2f85144f8eb2dde7a975861d9021b5c56b881f457b + languageName: node + linkType: hard + +"ci-info@npm:^4.0.0": + version: 4.0.0 + resolution: "ci-info@npm:4.0.0" + checksum: 122fe41c5eb8d0b5fa0ab6fd674c5ddcf2dc59766528b062a0144ff0d913cfb210ef925ec52110e7c2a7f4e603d5f0e8b91cfe68867e196e9212fa0b94d0a08a + languageName: node + linkType: hard + +"ci-parallel-vars@npm:^1.0.1": + version: 1.0.1 + resolution: "ci-parallel-vars@npm:1.0.1" + checksum: ae859831f7e8e3585db731b8306c336616e37bd709dad1d7775ea4c0731aefd94741dabb48201edc6827d000008fd7fb72cb977967614ee2d99d6b499f0c35fe + languageName: node + linkType: hard + "clean-stack@npm:^2.0.0": version: 2.2.0 resolution: "clean-stack@npm:2.2.0" @@ -533,6 +833,16 @@ __metadata: languageName: node linkType: hard +"cli-truncate@npm:^4.0.0": + version: 4.0.0 + resolution: "cli-truncate@npm:4.0.0" + dependencies: + slice-ansi: ^5.0.0 + string-width: ^7.0.0 + checksum: d5149175fd25ca985731bdeec46a55ec237475cf74c1a5e103baea696aceb45e372ac4acbaabf1316f06bd62e348123060f8191ffadfeedebd2a70a2a7fb199d + languageName: node + linkType: hard + "cliui@npm:^8.0.1": version: 8.0.1 resolution: "cliui@npm:8.0.1" @@ -544,6 +854,15 @@ __metadata: languageName: node linkType: hard +"code-excerpt@npm:^4.0.0": + version: 4.0.0 + resolution: "code-excerpt@npm:4.0.0" + dependencies: + convert-to-spaces: ^2.0.1 + checksum: d57137d8f4825879283a828cc02a1115b56858dc54ed06c625c8f67d6685d1becd2fbaa7f0ab19ecca1f5cca03f8c97bbc1f013cab40261e4d3275032e65efe9 + languageName: node + linkType: hard + "color-convert@npm:^1.9.0": version: 1.9.3 resolution: "color-convert@npm:1.9.3" @@ -576,6 +895,15 @@ __metadata: languageName: node linkType: hard +"color-support@npm:^1.1.2": + version: 1.1.3 + resolution: "color-support@npm:1.1.3" + bin: + color-support: bin.js + checksum: 9b7356817670b9a13a26ca5af1c21615463b500783b739b7634a0c2047c16cef4b2865d7576875c31c3cddf9dd621fa19285e628f20198b233a5cfdda6d0793b + languageName: node + linkType: hard + "colorette@npm:^2.0.20": version: 2.0.20 resolution: "colorette@npm:2.0.20" @@ -590,6 +918,13 @@ __metadata: languageName: node linkType: hard +"common-path-prefix@npm:^3.0.0": + version: 3.0.0 + resolution: "common-path-prefix@npm:3.0.0" + checksum: fdb3c4f54e51e70d417ccd950c07f757582de800c0678ca388aedefefc84982039f346f9fd9a1252d08d2da9e9ef4019f580a1d1d3a10da031e4bb3c924c5818 + languageName: node + linkType: hard + "concat-map@npm:0.0.1": version: 0.0.1 resolution: "concat-map@npm:0.0.1" @@ -597,6 +932,29 @@ __metadata: languageName: node linkType: hard +"concordance@npm:^5.0.4": + version: 5.0.4 + resolution: "concordance@npm:5.0.4" + dependencies: + date-time: ^3.1.0 + esutils: ^2.0.3 + fast-diff: ^1.2.0 + js-string-escape: ^1.0.1 + lodash: ^4.17.15 + md5-hex: ^3.0.1 + semver: ^7.3.2 + well-known-symbols: ^2.0.0 + checksum: 749153ba711492feb7c3d2f5bb04c107157440b3e39509bd5dd19ee7b3ac751d1e4cd75796d9f702e0a713312dbc661421c68aa4a2c34d5f6d91f47e3a1c64a6 + languageName: node + linkType: hard + +"console-control-strings@npm:^1.0.0, console-control-strings@npm:^1.1.0": + version: 1.1.0 + resolution: "console-control-strings@npm:1.1.0" + checksum: 8755d76787f94e6cf79ce4666f0c5519906d7f5b02d4b884cf41e11dcd759ed69c57da0670afd9236d229a46e0f9cf519db0cd829c6dca820bb5a5c3def584ed + languageName: node + linkType: hard + "convert-source-map@npm:^1.6.0": version: 1.9.0 resolution: "convert-source-map@npm:1.9.0" @@ -604,6 +962,13 @@ __metadata: languageName: node linkType: hard +"convert-to-spaces@npm:^2.0.1": + version: 2.0.1 + resolution: "convert-to-spaces@npm:2.0.1" + checksum: bbb324e5916fe9866f65c0ff5f9c1ea933764d0bdb09fccaf59542e40545ed483db6b2339c6d9eb56a11965a58f1a6038f3174f0e2fb7601343c7107ca5e2751 + languageName: node + linkType: hard + "cross-spawn@npm:^6.0.5": version: 6.0.5 resolution: "cross-spawn@npm:6.0.5" @@ -628,6 +993,24 @@ __metadata: languageName: node linkType: hard +"currently-unhandled@npm:^0.4.1": + version: 0.4.1 + resolution: "currently-unhandled@npm:0.4.1" + dependencies: + array-find-index: ^1.0.1 + checksum: 1f59fe10b5339b54b1a1eee110022f663f3495cf7cf2f480686e89edc7fa8bfe42dbab4b54f85034bc8b092a76cc7becbc2dad4f9adad332ab5831bec39ad540 + languageName: node + linkType: hard + +"date-time@npm:^3.1.0": + version: 3.1.0 + resolution: "date-time@npm:3.1.0" + dependencies: + time-zone: ^1.0.0 + checksum: f9cfcd1b15dfeabab15c0b9d18eb9e4e2d9d4371713564178d46a8f91ad577a290b5178b80050718d02d9c0cf646f8a875011e12d1ed05871e9f72c72c8a8fe6 + languageName: node + linkType: hard + "debug@npm:4, debug@npm:^4.3.4": version: 4.3.5 resolution: "debug@npm:4.3.5" @@ -652,6 +1035,20 @@ __metadata: languageName: node linkType: hard +"delegates@npm:^1.0.0": + version: 1.0.0 + resolution: "delegates@npm:1.0.0" + checksum: a51744d9b53c164ba9c0492471a1a2ffa0b6727451bdc89e31627fdf4adda9d51277cfcbfb20f0a6f08ccb3c436f341df3e92631a3440226d93a8971724771fd + languageName: node + linkType: hard + +"detect-libc@npm:^2.0.0": + version: 2.0.3 + resolution: "detect-libc@npm:2.0.3" + checksum: 2ba6a939ae55f189aea996ac67afceb650413c7a34726ee92c40fb0deb2400d57ef94631a8a3f052055eea7efb0f99a9b5e6ce923415daa3e68221f963cfc27d + languageName: node + linkType: hard + "eastasianwidth@npm:^0.2.0": version: 0.2.0 resolution: "eastasianwidth@npm:0.2.0" @@ -659,6 +1056,20 @@ __metadata: languageName: node linkType: hard +"emittery@npm:^1.0.1": + version: 1.0.3 + resolution: "emittery@npm:1.0.3" + checksum: c9e760431294a546dacc236e563ee29cc650374696ef5f824a465a4a7c584ca2c0046885a3e5d7cd3d9592713200c82f4a4ded11d0b49c06cb5bb587dedc46b4 + languageName: node + linkType: hard + +"emoji-regex@npm:^10.3.0": + version: 10.3.0 + resolution: "emoji-regex@npm:10.3.0" + checksum: 5da48edfeb9462fb1ae5495cff2d79129974c696853fb0ce952cbf560f29a2756825433bf51cfd5157ec7b9f93f46f31d712e896d63e3d8ac9c3832bdb45ab73 + languageName: node + linkType: hard + "emoji-regex@npm:^8.0.0": version: 8.0.0 resolution: "emoji-regex@npm:8.0.0" @@ -799,6 +1210,44 @@ __metadata: languageName: node linkType: hard +"escape-string-regexp@npm:^2.0.0": + version: 2.0.0 + resolution: "escape-string-regexp@npm:2.0.0" + checksum: 9f8a2d5743677c16e85c810e3024d54f0c8dea6424fad3c79ef6666e81dd0846f7437f5e729dfcdac8981bc9e5294c39b4580814d114076b8d36318f46ae4395 + languageName: node + linkType: hard + +"escape-string-regexp@npm:^5.0.0": + version: 5.0.0 + resolution: "escape-string-regexp@npm:5.0.0" + checksum: 20daabe197f3cb198ec28546deebcf24b3dbb1a5a269184381b3116d12f0532e06007f4bc8da25669d6a7f8efb68db0758df4cd981f57bc5b57f521a3e12c59e + languageName: node + linkType: hard + +"esprima@npm:^4.0.0": + version: 4.0.1 + resolution: "esprima@npm:4.0.1" + bin: + esparse: ./bin/esparse.js + esvalidate: ./bin/esvalidate.js + checksum: b45bc805a613dbea2835278c306b91aff6173c8d034223fa81498c77dcbce3b2931bf6006db816f62eacd9fd4ea975dfd85a5b7f3c6402cfd050d4ca3c13a628 + languageName: node + linkType: hard + +"estree-walker@npm:2.0.2, estree-walker@npm:^2.0.1": + version: 2.0.2 + resolution: "estree-walker@npm:2.0.2" + checksum: 6151e6f9828abe2259e57f5fd3761335bb0d2ebd76dc1a01048ccee22fabcfef3c0859300f6d83ff0d1927849368775ec5a6d265dde2f6de5a1be1721cd94efc + languageName: node + linkType: hard + +"esutils@npm:^2.0.3": + version: 2.0.3 + resolution: "esutils@npm:2.0.3" + checksum: 22b5b08f74737379a840b8ed2036a5fb35826c709ab000683b092d9054e5c2a82c27818f12604bfc2a9a76b90b6834ef081edbc1c7ae30d1627012e067c6ec87 + languageName: node + linkType: hard + "eventemitter3@npm:^5.0.1": version: 5.0.1 resolution: "eventemitter3@npm:5.0.1" @@ -830,6 +1279,51 @@ __metadata: languageName: node linkType: hard +"fast-diff@npm:^1.2.0": + version: 1.3.0 + resolution: "fast-diff@npm:1.3.0" + checksum: d22d371b994fdc8cce9ff510d7b8dc4da70ac327bcba20df607dd5b9cae9f908f4d1028f5fe467650f058d1e7270235ae0b8230809a262b4df587a3b3aa216c3 + languageName: node + linkType: hard + +"fast-glob@npm:^3.3.2": + version: 3.3.2 + resolution: "fast-glob@npm:3.3.2" + dependencies: + "@nodelib/fs.stat": ^2.0.2 + "@nodelib/fs.walk": ^1.2.3 + glob-parent: ^5.1.2 + merge2: ^1.3.0 + micromatch: ^4.0.4 + checksum: 900e4979f4dbc3313840078419245621259f349950411ca2fa445a2f9a1a6d98c3b5e7e0660c5ccd563aa61abe133a21765c6c0dec8e57da1ba71d8000b05ec1 + languageName: node + linkType: hard + +"fastq@npm:^1.6.0": + version: 1.17.1 + resolution: "fastq@npm:1.17.1" + dependencies: + reusify: ^1.0.4 + checksum: a8c5b26788d5a1763f88bae56a8ddeee579f935a831c5fe7a8268cea5b0a91fbfe705f612209e02d639b881d7b48e461a50da4a10cfaa40da5ca7cc9da098d88 + languageName: node + linkType: hard + +"figures@npm:^6.0.1": + version: 6.1.0 + resolution: "figures@npm:6.1.0" + dependencies: + is-unicode-supported: ^2.0.0 + checksum: 35c81239d4fa40b75c2c7c010833b0bc8861c27187e4c9388fca1d9731103ec9989b70ee3b664ef426ddd9abe02ec5f4fd973424aa8c6fd3ea5d3bf57a2d01b4 + languageName: node + linkType: hard + +"file-uri-to-path@npm:1.0.0": + version: 1.0.0 + resolution: "file-uri-to-path@npm:1.0.0" + checksum: b648580bdd893a008c92c7ecc96c3ee57a5e7b6c4c18a9a09b44fb5d36d79146f8e442578bc0e173dc027adf3987e254ba1dfd6e3ec998b7c282873010502144 + languageName: node + linkType: hard + "fill-range@npm:^7.0.1": version: 7.0.1 resolution: "fill-range@npm:7.0.1" @@ -839,6 +1333,22 @@ __metadata: languageName: node linkType: hard +"fill-range@npm:^7.1.1": + version: 7.1.1 + resolution: "fill-range@npm:7.1.1" + dependencies: + to-regex-range: ^5.0.1 + checksum: b4abfbca3839a3d55e4ae5ec62e131e2e356bf4859ce8480c64c4876100f4df292a63e5bb1618e1d7460282ca2b305653064f01654474aa35c68000980f17798 + languageName: node + linkType: hard + +"find-up-simple@npm:^1.0.0": + version: 1.0.0 + resolution: "find-up-simple@npm:1.0.0" + checksum: 91c3d51c1111b5eb4e6e6d71d21438f6571a37a69dc288d4222b98996756e2f472fa5393a4dddb5e1a84929405d87e86f4bdce798ba84ee513b79854960ec140 + languageName: node + linkType: hard + "find-up@npm:^5.0.0": version: 5.0.0 resolution: "find-up@npm:5.0.0" @@ -913,6 +1423,23 @@ __metadata: languageName: node linkType: hard +"gauge@npm:^3.0.0": + version: 3.0.2 + resolution: "gauge@npm:3.0.2" + dependencies: + aproba: ^1.0.3 || ^2.0.0 + color-support: ^1.1.2 + console-control-strings: ^1.0.0 + has-unicode: ^2.0.1 + object-assign: ^4.1.1 + signal-exit: ^3.0.0 + string-width: ^4.2.3 + strip-ansi: ^6.0.1 + wide-align: ^1.1.2 + checksum: 81296c00c7410cdd48f997800155fbead4f32e4f82109be0719c63edc8560e6579946cc8abd04205297640691ec26d21b578837fd13a4e96288ab4b40b1dc3e9 + languageName: node + linkType: hard + "get-caller-file@npm:^2.0.5": version: 2.0.5 resolution: "get-caller-file@npm:2.0.5" @@ -920,6 +1447,13 @@ __metadata: languageName: node linkType: hard +"get-east-asian-width@npm:^1.0.0": + version: 1.2.0 + resolution: "get-east-asian-width@npm:1.2.0" + checksum: ea55f4d4a42c4b00d3d9be3111bc17eb0161f60ed23fc257c1390323bb780a592d7a8bdd550260fd4627dabee9a118cdfa3475ae54edca35ebcd3bdae04179e3 + languageName: node + linkType: hard + "get-stream@npm:^6.0.1": version: 6.0.1 resolution: "get-stream@npm:6.0.1" @@ -936,6 +1470,15 @@ __metadata: languageName: node linkType: hard +"glob-parent@npm:^5.1.2": + version: 5.1.2 + resolution: "glob-parent@npm:5.1.2" + dependencies: + is-glob: ^4.0.1 + checksum: f4f2bfe2425296e8a47e36864e4f42be38a996db40420fe434565e4480e3322f18eb37589617a98640c5dc8fdec1a387007ee18dbb1f3f5553409c34d17f425e + languageName: node + linkType: hard + "glob@npm:^10.2.2, glob@npm:^10.3.10": version: 10.4.2 resolution: "glob@npm:10.4.2" @@ -966,7 +1509,21 @@ __metadata: languageName: node linkType: hard -"graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.2.6": +"globby@npm:^14.0.0": + version: 14.0.2 + resolution: "globby@npm:14.0.2" + dependencies: + "@sindresorhus/merge-streams": ^2.1.0 + fast-glob: ^3.3.2 + ignore: ^5.2.4 + path-type: ^5.0.0 + slash: ^5.1.0 + unicorn-magic: ^0.1.0 + checksum: 2cee79efefca4383a825fc2fcbdb37e5706728f2d39d4b63851927c128fff62e6334ef7d4d467949d411409ad62767dc2d214e0f837a0f6d4b7290b6711d485c + languageName: node + linkType: hard + +"graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": version: 4.2.11 resolution: "graceful-fs@npm:4.2.11" checksum: ac85f94da92d8eb6b7f5a8b20ce65e43d66761c55ce85ac96df6865308390da45a8d3f0296dd3a663de65d30ba497bd46c696cc1e248c72b13d6d567138a4fc7 @@ -987,6 +1544,13 @@ __metadata: languageName: node linkType: hard +"has-unicode@npm:^2.0.1": + version: 2.0.1 + resolution: "has-unicode@npm:2.0.1" + checksum: 1eab07a7436512db0be40a710b29b5dc21fa04880b7f63c9980b706683127e3c1b57cb80ea96d47991bdae2dfe479604f6a1ba410106ee1046a41d1bd0814400 + languageName: node + linkType: hard + "has@npm:@nolyfill/has@latest": version: 1.0.20 resolution: "@nolyfill/has@npm:1.0.20" @@ -1027,6 +1591,16 @@ __metadata: languageName: node linkType: hard +"https-proxy-agent@npm:^5.0.0": + version: 5.0.1 + resolution: "https-proxy-agent@npm:5.0.1" + dependencies: + agent-base: 6 + debug: 4 + checksum: 571fccdf38184f05943e12d37d6ce38197becdd69e58d03f43637f7fa1269cf303a7d228aa27e5b27bbd3af8f09fd938e1c91dcfefff2df7ba77c20ed8dfc765 + languageName: node + linkType: hard + "https-proxy-agent@npm:^7.0.1": version: 7.0.5 resolution: "https-proxy-agent@npm:7.0.5" @@ -1062,6 +1636,20 @@ __metadata: languageName: node linkType: hard +"ignore-by-default@npm:^2.1.0": + version: 2.1.0 + resolution: "ignore-by-default@npm:2.1.0" + checksum: 2b2df4622b6a07a3e91893987be8f060dc553f7736b67e72aa2312041c450a6fa8371733d03c42f45a02e47ec824e961c2fba63a3d94fc59cbd669220a5b0d7a + languageName: node + linkType: hard + +"ignore@npm:^5.2.4": + version: 5.3.1 + resolution: "ignore@npm:5.3.1" + checksum: 71d7bb4c1dbe020f915fd881108cbe85a0db3d636a0ea3ba911393c53946711d13a9b1143c7e70db06d571a5822c0a324a6bcde5c9904e7ca5047f01f1bf8cd3 + languageName: node + linkType: hard + "imurmurhash@npm:^0.1.4": version: 0.1.4 resolution: "imurmurhash@npm:0.1.4" @@ -1076,6 +1664,13 @@ __metadata: languageName: node linkType: hard +"indent-string@npm:^5.0.0": + version: 5.0.0 + resolution: "indent-string@npm:5.0.0" + checksum: e466c27b6373440e6d84fbc19e750219ce25865cb82d578e41a6053d727e5520dc5725217d6eb1cc76005a1bb1696a0f106d84ce7ebda3033b963a38583fb3b3 + languageName: node + linkType: hard + "inflight@npm:^1.0.4": version: 1.0.6 resolution: "inflight@npm:1.0.6" @@ -1086,7 +1681,7 @@ __metadata: languageName: node linkType: hard -"inherits@npm:2": +"inherits@npm:2, inherits@npm:^2.0.3": version: 2.0.4 resolution: "inherits@npm:2.0.4" checksum: 4a48a733847879d6cf6691860a6b1e3f0f4754176e4d71494c41f3475553768b10f84b5ce1d40fbd0e34e6bfbb864ee35858ad4dd2cf31e02fc4a154b724d7f1 @@ -1103,6 +1698,13 @@ __metadata: languageName: node linkType: hard +"irregular-plurals@npm:^3.3.0": + version: 3.5.0 + resolution: "irregular-plurals@npm:3.5.0" + checksum: 5b663091dc89155df7b2e9d053e8fb11941a0c4be95c4b6549ed3ea020489fdf4f75ea586c915b5b543704252679a5a6e8c6c3587da5ac3fc57b12da90a9aee7 + languageName: node + linkType: hard + "is-arrayish@npm:^0.2.1": version: 0.2.1 resolution: "is-arrayish@npm:0.2.1" @@ -1119,6 +1721,13 @@ __metadata: languageName: node linkType: hard +"is-extglob@npm:^2.1.1": + version: 2.1.1 + resolution: "is-extglob@npm:2.1.1" + checksum: df033653d06d0eb567461e58a7a8c9f940bd8c22274b94bf7671ab36df5719791aae15eef6d83bbb5e23283967f2f984b8914559d4449efda578c775c4be6f85 + languageName: node + linkType: hard + "is-fullwidth-code-point@npm:^3.0.0": version: 3.0.0 resolution: "is-fullwidth-code-point@npm:3.0.0" @@ -1133,6 +1742,15 @@ __metadata: languageName: node linkType: hard +"is-glob@npm:^4.0.1": + version: 4.0.3 + resolution: "is-glob@npm:4.0.3" + dependencies: + is-extglob: ^2.1.1 + checksum: d381c1319fcb69d341cc6e6c7cd588e17cd94722d9a32dbd60660b993c4fb7d0f19438674e68dfec686d09b7c73139c9166b47597f846af387450224a8101ab4 + languageName: node + linkType: hard + "is-lambda@npm:^1.0.1": version: 1.0.1 resolution: "is-lambda@npm:1.0.1" @@ -1147,6 +1765,20 @@ __metadata: languageName: node linkType: hard +"is-plain-object@npm:^5.0.0": + version: 5.0.0 + resolution: "is-plain-object@npm:5.0.0" + checksum: e32d27061eef62c0847d303125440a38660517e586f2f3db7c9d179ae5b6674ab0f469d519b2e25c147a1a3bc87156d0d5f4d8821e0ce4a9ee7fe1fcf11ce45c + languageName: node + linkType: hard + +"is-promise@npm:^4.0.0": + version: 4.0.0 + resolution: "is-promise@npm:4.0.0" + checksum: 0b46517ad47b00b6358fd6553c83ec1f6ba9acd7ffb3d30a0bf519c5c69e7147c132430452351b8a9fc198f8dd6c4f76f8e6f5a7f100f8c77d57d9e0f4261a8a + languageName: node + linkType: hard + "is-stream@npm:^3.0.0": version: 3.0.0 resolution: "is-stream@npm:3.0.0" @@ -1154,6 +1786,13 @@ __metadata: languageName: node linkType: hard +"is-unicode-supported@npm:^2.0.0": + version: 2.0.0 + resolution: "is-unicode-supported@npm:2.0.0" + checksum: 000b80639dedaf59a385f1c0a57f97a4d1435e0723716f24cc19ad94253a7a0a9f838bdc9ac49b10a29ac93b01f52ae9b2ed358a8876caf1eb74d73b4ede92b2 + languageName: node + linkType: hard + "isexe@npm:^2.0.0": version: 2.0.0 resolution: "isexe@npm:2.0.0" @@ -1216,6 +1855,25 @@ __metadata: languageName: node linkType: hard +"js-string-escape@npm:^1.0.1": + version: 1.0.1 + resolution: "js-string-escape@npm:1.0.1" + checksum: f11e0991bf57e0c183b55c547acec85bd2445f043efc9ea5aa68b41bd2a3e7d3ce94636cb233ae0d84064ba4c1a505d32e969813c5b13f81e7d4be12c59256fe + languageName: node + linkType: hard + +"js-yaml@npm:^3.14.1": + version: 3.14.1 + resolution: "js-yaml@npm:3.14.1" + dependencies: + argparse: ^1.0.7 + esprima: ^4.0.0 + bin: + js-yaml: bin/js-yaml.js + checksum: bef146085f472d44dee30ec34e5cf36bf89164f5d585435a3d3da89e52622dff0b188a580e4ad091c3341889e14cb88cac6e4deb16dc5b1e9623bb0601fc255c + languageName: node + linkType: hard + "jsbn@npm:1.1.0": version: 1.1.0 resolution: "jsbn@npm:1.1.0" @@ -1308,6 +1966,13 @@ __metadata: languageName: node linkType: hard +"load-json-file@npm:^7.0.1": + version: 7.0.1 + resolution: "load-json-file@npm:7.0.1" + checksum: a560288da6891778321ef993e4bdbdf05374a4f3a3aeedd5ba6b64672798c830d748cfc59a2ec9891a3db30e78b3d04172e0dcb0d4828168289a393147ca0e74 + languageName: node + linkType: hard + "locate-path@npm:^6.0.0": version: 6.0.0 resolution: "locate-path@npm:6.0.0" @@ -1317,6 +1982,13 @@ __metadata: languageName: node linkType: hard +"lodash@npm:^4.17.15": + version: 4.17.21 + resolution: "lodash@npm:4.17.21" + checksum: eb835a2e51d381e561e508ce932ea50a8e5a68f4ebdd771ea240d3048244a8d13658acbd502cd4829768c56f2e16bdd4340b9ea141297d472517b83868e677f7 + languageName: node + linkType: hard + "log-update@npm:^5.0.1": version: 5.0.1 resolution: "log-update@npm:5.0.1" @@ -1337,6 +2009,15 @@ __metadata: languageName: node linkType: hard +"make-dir@npm:^3.1.0": + version: 3.1.0 + resolution: "make-dir@npm:3.1.0" + dependencies: + semver: ^6.0.0 + checksum: 484200020ab5a1fdf12f393fe5f385fc8e4378824c940fba1729dcd198ae4ff24867bc7a5646331e50cead8abff5d9270c456314386e629acec6dff4b8016b78 + languageName: node + linkType: hard + "make-dir@npm:^4.0.0": version: 4.0.0 resolution: "make-dir@npm:4.0.0" @@ -1366,6 +2047,33 @@ __metadata: languageName: node linkType: hard +"matcher@npm:^5.0.0": + version: 5.0.0 + resolution: "matcher@npm:5.0.0" + dependencies: + escape-string-regexp: ^5.0.0 + checksum: 28f191c2d23fee0f6f32fd0181d9fe173b0ab815a919edba55605438a2f9fa40372e002574a1b17add981b0a8669c75bc6194318d065ed2dceffd8b160c38118 + languageName: node + linkType: hard + +"md5-hex@npm:^3.0.1": + version: 3.0.1 + resolution: "md5-hex@npm:3.0.1" + dependencies: + blueimp-md5: ^2.10.0 + checksum: 6799a19e8bdd3e0c2861b94c1d4d858a89220488d7885c1fa236797e367d0c2e5f2b789e05309307083503f85be3603a9686a5915568a473137d6b4117419cc2 + languageName: node + linkType: hard + +"memoize@npm:^10.0.0": + version: 10.0.0 + resolution: "memoize@npm:10.0.0" + dependencies: + mimic-function: ^5.0.0 + checksum: a052912fcd4f1e258438abd800c15c6be6cc7123d3affbbcc438e95c11c5d7e0209882f8e58938e92d62a435b839cc359f69674b455d906596a484cbbe29727d + languageName: node + linkType: hard + "memorystream@npm:^0.3.1": version: 0.3.1 resolution: "memorystream@npm:0.3.1" @@ -1380,6 +2088,13 @@ __metadata: languageName: node linkType: hard +"merge2@npm:^1.3.0": + version: 1.4.1 + resolution: "merge2@npm:1.4.1" + checksum: 7268db63ed5169466540b6fb947aec313200bcf6d40c5ab722c22e242f651994619bcd85601602972d3c85bd2cc45a358a4c61937e9f11a061919a1da569b0c2 + languageName: node + linkType: hard + "micromatch@npm:4.0.5": version: 4.0.5 resolution: "micromatch@npm:4.0.5" @@ -1390,6 +2105,16 @@ __metadata: languageName: node linkType: hard +"micromatch@npm:^4.0.2, micromatch@npm:^4.0.4": + version: 4.0.7 + resolution: "micromatch@npm:4.0.7" + dependencies: + braces: ^3.0.3 + picomatch: ^2.3.1 + checksum: 3cde047d70ad80cf60c787b77198d680db3b8c25b23feb01de5e2652205d9c19f43bd81882f69a0fd1f0cde6a7a122d774998aad3271ddb1b8accf8a0f480cf7 + languageName: node + linkType: hard + "mimic-fn@npm:^2.1.0": version: 2.1.0 resolution: "mimic-fn@npm:2.1.0" @@ -1404,6 +2129,13 @@ __metadata: languageName: node linkType: hard +"mimic-function@npm:^5.0.0": + version: 5.0.1 + resolution: "mimic-function@npm:5.0.1" + checksum: eb5893c99e902ccebbc267c6c6b83092966af84682957f79313311edb95e8bb5f39fb048d77132b700474d1c86d90ccc211e99bae0935447a4834eb4c882982c + languageName: node + linkType: hard + "minimatch@npm:^3.0.4, minimatch@npm:^3.1.1": version: 3.1.2 resolution: "minimatch@npm:3.1.2" @@ -1522,6 +2254,13 @@ __metadata: languageName: node linkType: hard +"ms@npm:^2.1.3": + version: 2.1.3 + resolution: "ms@npm:2.1.3" + checksum: aa92de608021b242401676e35cfa5aa42dd70cbdc082b916da7fb925c542173e36bce97ea3e804923fe92c0ad991434e4a38327e15a1b5b5f945d66df615ae6d + languageName: node + linkType: hard + "negotiator@npm:^0.6.3": version: 0.6.3 resolution: "negotiator@npm:0.6.3" @@ -1536,6 +2275,31 @@ __metadata: languageName: node linkType: hard +"node-fetch@npm:^2.6.7": + version: 2.7.0 + resolution: "node-fetch@npm:2.7.0" + dependencies: + whatwg-url: ^5.0.0 + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + checksum: d76d2f5edb451a3f05b15115ec89fc6be39de37c6089f1b6368df03b91e1633fd379a7e01b7ab05089a25034b2023d959b47e59759cb38d88341b2459e89d6e5 + languageName: node + linkType: hard + +"node-gyp-build@npm:^4.2.2": + version: 4.8.1 + resolution: "node-gyp-build@npm:4.8.1" + bin: + node-gyp-build: bin.js + node-gyp-build-optional: optional.js + node-gyp-build-test: build-test.js + checksum: fe6e95da6f4608c1a98655f6bf2fe4e8dd9c877cd13256056a8acaf585cc7f98718823fe9366be11b78c2f332d5a184b00cf07a4af96c9d8fea45f640c019f98 + languageName: node + linkType: hard + "node-gyp@npm:latest": version: 10.1.0 resolution: "node-gyp@npm:10.1.0" @@ -1556,6 +2320,24 @@ __metadata: languageName: node linkType: hard +"nofilter@npm:^3.1.0": + version: 3.1.0 + resolution: "nofilter@npm:3.1.0" + checksum: 58aa85a5b4b35cbb6e42de8a8591c5e338061edc9f3e7286f2c335e9e9b9b8fa7c335ae45daa8a1f3433164dc0b9a3d187fa96f9516e04a17a1f9ce722becc4f + languageName: node + linkType: hard + +"nopt@npm:^5.0.0": + version: 5.0.0 + resolution: "nopt@npm:5.0.0" + dependencies: + abbrev: 1 + bin: + nopt: bin/nopt.js + checksum: d35fdec187269503843924e0114c0c6533fb54bbf1620d0f28b4b60ba01712d6687f62565c55cc20a504eff0fbe5c63e22340c3fad549ad40469ffb611b04f2f + languageName: node + linkType: hard + "nopt@npm:^7.0.0": version: 7.2.1 resolution: "nopt@npm:7.2.1" @@ -1609,6 +2391,25 @@ __metadata: languageName: node linkType: hard +"npmlog@npm:^5.0.1": + version: 5.0.1 + resolution: "npmlog@npm:5.0.1" + dependencies: + are-we-there-yet: ^2.0.0 + console-control-strings: ^1.1.0 + gauge: ^3.0.0 + set-blocking: ^2.0.0 + checksum: 516b2663028761f062d13e8beb3f00069c5664925871a9b57989642ebe09f23ab02145bf3ab88da7866c4e112cafff72401f61a672c7c8a20edc585a7016ef5f + languageName: node + linkType: hard + +"object-assign@npm:^4.1.1": + version: 4.1.1 + resolution: "object-assign@npm:4.1.1" + checksum: fcc6e4ea8c7fe48abfbb552578b1c53e0d194086e2e6bbbf59e0a536381a292f39943c6e9628af05b5528aa5e3318bb30d6b2e53cadaf5b8fe9e12c4b69af23f + languageName: node + linkType: hard + "once@npm:^1.3.0": version: 1.4.0 resolution: "once@npm:1.4.0" @@ -1663,6 +2464,23 @@ __metadata: languageName: node linkType: hard +"p-map@npm:^7.0.1": + version: 7.0.2 + resolution: "p-map@npm:7.0.2" + checksum: bc128c2b244ef5d4619392b2247d718a3fe471d5fa4a73834fd96182a237f460ec7e0ad0f95139ef7103a6b50ed164228c62e2f8e41ba2b15360fe1c20d13563 + languageName: node + linkType: hard + +"package-config@npm:^5.0.0": + version: 5.0.0 + resolution: "package-config@npm:5.0.0" + dependencies: + find-up-simple: ^1.0.0 + load-json-file: ^7.0.1 + checksum: dfff5264c51a0dad7af9a55b02e3b8b6e457075e9c4f02d0ffacfeee9af4dd5db2b566dae41486412161292b8741483cd89d5a8404a5742fc54d718dadacac4a + languageName: node + linkType: hard + "package-json-from-dist@npm:^1.0.0": version: 1.0.0 resolution: "package-json-from-dist@npm:1.0.0" @@ -1680,6 +2498,13 @@ __metadata: languageName: node linkType: hard +"parse-ms@npm:^4.0.0": + version: 4.0.0 + resolution: "parse-ms@npm:4.0.0" + checksum: 673c801d9f957ff79962d71ed5a24850163f4181a90dd30c4e3666b3a804f53b77f1f0556792e8b2adbb5d58757907d1aa51d7d7dc75997c2a56d72937cbc8b7 + languageName: node + linkType: hard + "path-exists@npm:^4.0.0": version: 4.0.0 resolution: "path-exists@npm:4.0.0" @@ -1741,13 +2566,27 @@ __metadata: languageName: node linkType: hard -"picomatch@npm:^2.3.1": +"path-type@npm:^5.0.0": + version: 5.0.0 + resolution: "path-type@npm:5.0.0" + checksum: 15ec24050e8932c2c98d085b72cfa0d6b4eeb4cbde151a0a05726d8afae85784fc5544f733d8dfc68536587d5143d29c0bd793623fad03d7e61cc00067291cd5 + languageName: node + linkType: hard + +"picomatch@npm:^2.2.2, picomatch@npm:^2.3.1": version: 2.3.1 resolution: "picomatch@npm:2.3.1" checksum: 050c865ce81119c4822c45d3c84f1ced46f93a0126febae20737bd05ca20589c564d6e9226977df859ed5e03dc73f02584a2b0faad36e896936238238b0446cf languageName: node linkType: hard +"picomatch@npm:^3.0.1": + version: 3.0.1 + resolution: "picomatch@npm:3.0.1" + checksum: b7fe18174bcc05bbf0ea09cc85623ae395676b3e6bc25636d4c20db79a948586237e429905453bf1ba385bc7a7aa5b56f1b351680e650d2b5c305ceb98dfc914 + languageName: node + linkType: hard + "pidtree@npm:0.6.0": version: 0.6.0 resolution: "pidtree@npm:0.6.0" @@ -1773,6 +2612,15 @@ __metadata: languageName: node linkType: hard +"plur@npm:^5.1.0": + version: 5.1.0 + resolution: "plur@npm:5.1.0" + dependencies: + irregular-plurals: ^3.3.0 + checksum: 57e400dc4b926768fb0abab7f8688fe17e85673712134546e7beaaee188bae7e0504976e847d7e41d0d6103ff2fd61204095f03c2a45de19a8bad15aecb45cc1 + languageName: node + linkType: hard + "prettier@npm:^3.0.2": version: 3.0.2 resolution: "prettier@npm:3.0.2" @@ -1782,6 +2630,15 @@ __metadata: languageName: node linkType: hard +"pretty-ms@npm:^9.0.0": + version: 9.0.0 + resolution: "pretty-ms@npm:9.0.0" + dependencies: + parse-ms: ^4.0.0 + checksum: 072b17547e09cb232e8e4c7be0281e256b6d8acd18dfb2fdd715d50330d1689fdaa877f53cf90c62ed419ef842f0f5fb94a2cd8ed1aa6d7608ad48834219435d + languageName: node + linkType: hard + "proc-log@npm:^3.0.0": version: 3.0.0 resolution: "proc-log@npm:3.0.0" @@ -1816,6 +2673,13 @@ __metadata: languageName: node linkType: hard +"queue-microtask@npm:^1.2.2": + version: 1.2.3 + resolution: "queue-microtask@npm:1.2.3" + checksum: b676f8c040cdc5b12723ad2f91414d267605b26419d5c821ff03befa817ddd10e238d22b25d604920340fd73efd8ba795465a0377c4adf45a4a41e4234e42dc4 + languageName: node + linkType: hard + "read-pkg@npm:^3.0.0": version: 3.0.0 resolution: "read-pkg@npm:3.0.0" @@ -1827,6 +2691,17 @@ __metadata: languageName: node linkType: hard +"readable-stream@npm:^3.6.0": + version: 3.6.2 + resolution: "readable-stream@npm:3.6.2" + dependencies: + inherits: ^2.0.3 + string_decoder: ^1.1.1 + util-deprecate: ^1.0.1 + checksum: bdcbe6c22e846b6af075e32cf8f4751c2576238c5043169a1c221c92ee2878458a816a4ea33f4c67623c0b6827c8a400409bfb3cf0bf3381392d0b1dfb52ac8d + languageName: node + linkType: hard + "require-directory@npm:^2.1.1": version: 2.1.1 resolution: "require-directory@npm:2.1.1" @@ -1834,6 +2709,22 @@ __metadata: languageName: node linkType: hard +"resolve-cwd@npm:^3.0.0": + version: 3.0.0 + resolution: "resolve-cwd@npm:3.0.0" + dependencies: + resolve-from: ^5.0.0 + checksum: 546e0816012d65778e580ad62b29e975a642989108d9a3c5beabfb2304192fa3c9f9146fbdfe213563c6ff51975ae41bac1d3c6e047dd9572c94863a057b4d81 + languageName: node + linkType: hard + +"resolve-from@npm:^5.0.0": + version: 5.0.0 + resolution: "resolve-from@npm:5.0.0" + checksum: 4ceeb9113e1b1372d0cd969f3468fa042daa1dd9527b1b6bb88acb6ab55d8b9cd65dbf18819f9f9ddf0db804990901dcdaade80a215e7b2c23daae38e64f5bdf + languageName: node + linkType: hard + "resolve-pkg-maps@npm:^1.0.0": version: 1.0.0 resolution: "resolve-pkg-maps@npm:1.0.0" @@ -1884,6 +2775,13 @@ __metadata: languageName: node linkType: hard +"reusify@npm:^1.0.4": + version: 1.0.4 + resolution: "reusify@npm:1.0.4" + checksum: c3076ebcc22a6bc252cb0b9c77561795256c22b757f40c0d8110b1300723f15ec0fc8685e8d4ea6d7666f36c79ccc793b1939c748bf36f18f542744a4e379fcc + languageName: node + linkType: hard + "rfdc@npm:^1.3.0": version: 1.3.0 resolution: "rfdc@npm:1.3.0" @@ -1902,6 +2800,22 @@ __metadata: languageName: node linkType: hard +"run-parallel@npm:^1.1.9": + version: 1.2.0 + resolution: "run-parallel@npm:1.2.0" + dependencies: + queue-microtask: ^1.2.2 + checksum: cb4f97ad25a75ebc11a8ef4e33bb962f8af8516bb2001082ceabd8902e15b98f4b84b4f8a9b222e5d57fc3bd1379c483886ed4619367a7680dad65316993021d + languageName: node + linkType: hard + +"safe-buffer@npm:~5.2.0": + version: 5.2.1 + resolution: "safe-buffer@npm:5.2.1" + checksum: b99c4b41fdd67a6aaf280fcd05e9ffb0813654894223afb78a31f14a19ad220bba8aba1cb14eddce1fcfb037155fe6de4e861784eb434f7d11ed58d1e70dd491 + languageName: node + linkType: hard + "safer-buffer@npm:>= 2.1.2 < 3.0.0": version: 2.1.2 resolution: "safer-buffer@npm:2.1.2" @@ -1918,7 +2832,16 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.3.5, semver@npm:^7.5.3": +"semver@npm:^6.0.0": + version: 6.3.1 + resolution: "semver@npm:6.3.1" + bin: + semver: bin/semver.js + checksum: ae47d06de28836adb9d3e25f22a92943477371292d9b665fb023fae278d345d508ca1958232af086d85e0155aee22e313e100971898bbb8d5d89b8b1d4054ca2 + languageName: node + linkType: hard + +"semver@npm:^7.3.2, semver@npm:^7.3.5, semver@npm:^7.5.3": version: 7.6.2 resolution: "semver@npm:7.6.2" bin: @@ -1927,6 +2850,22 @@ __metadata: languageName: node linkType: hard +"serialize-error@npm:^7.0.1": + version: 7.0.1 + resolution: "serialize-error@npm:7.0.1" + dependencies: + type-fest: ^0.13.1 + checksum: e0aba4dca2fc9fe74ae1baf38dbd99190e1945445a241ba646290f2176cdb2032281a76443b02ccf0caf30da5657d510746506368889a593b9835a497fc0732e + languageName: node + linkType: hard + +"set-blocking@npm:^2.0.0": + version: 2.0.0 + resolution: "set-blocking@npm:2.0.0" + checksum: 6e65a05f7cf7ebdf8b7c75b101e18c0b7e3dff4940d480efed8aad3a36a4005140b660fa1d804cb8bce911cac290441dc728084a30504d3516ac2ff7ad607b02 + languageName: node + linkType: hard + "shebang-command@npm:^1.2.0": version: 1.2.0 resolution: "shebang-command@npm:1.2.0" @@ -1966,7 +2905,7 @@ __metadata: languageName: node linkType: hard -"signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.7": +"signal-exit@npm:^3.0.0, signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.7": version: 3.0.7 resolution: "signal-exit@npm:3.0.7" checksum: a2f098f247adc367dffc27845853e9959b9e88b01cb301658cfe4194352d8d2bb32e18467c786a7fe15f1d44b233ea35633d076d5e737870b7139949d1ab6318 @@ -1987,6 +2926,13 @@ __metadata: languageName: node linkType: hard +"slash@npm:^5.1.0": + version: 5.1.0 + resolution: "slash@npm:5.1.0" + checksum: 70434b34c50eb21b741d37d455110258c42d2cf18c01e6518aeb7299f3c6e626330c889c0c552b5ca2ef54a8f5a74213ab48895f0640717cacefeef6830a1ba4 + languageName: node + linkType: hard + "slice-ansi@npm:^5.0.0": version: 5.0.0 resolution: "slice-ansi@npm:5.0.0" @@ -2066,6 +3012,13 @@ __metadata: languageName: node linkType: hard +"sprintf-js@npm:~1.0.2": + version: 1.0.3 + resolution: "sprintf-js@npm:1.0.3" + checksum: 19d79aec211f09b99ec3099b5b2ae2f6e9cdefe50bc91ac4c69144b6d3928a640bb6ae5b3def70c2e85a2c3d9f5ec2719921e3a59d3ca3ef4b2fd1a4656a0df3 + languageName: node + linkType: hard + "ssri@npm:^10.0.0": version: 10.0.6 resolution: "ssri@npm:10.0.6" @@ -2075,6 +3028,15 @@ __metadata: languageName: node linkType: hard +"stack-utils@npm:^2.0.6": + version: 2.0.6 + resolution: "stack-utils@npm:2.0.6" + dependencies: + escape-string-regexp: ^2.0.0 + checksum: 052bf4d25bbf5f78e06c1d5e67de2e088b06871fa04107ca8d3f0e9d9263326e2942c8bedee3545795fc77d787d443a538345eef74db2f8e35db3558c6f91ff7 + languageName: node + linkType: hard + "string-argv@npm:0.3.2": version: 0.3.2 resolution: "string-argv@npm:0.3.2" @@ -2082,7 +3044,7 @@ __metadata: languageName: node linkType: hard -"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": +"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": version: 4.2.3 resolution: "string-width@npm:4.2.3" dependencies: @@ -2104,6 +3066,17 @@ __metadata: languageName: node linkType: hard +"string-width@npm:^7.0.0": + version: 7.2.0 + resolution: "string-width@npm:7.2.0" + dependencies: + emoji-regex: ^10.3.0 + get-east-asian-width: ^1.0.0 + strip-ansi: ^7.1.0 + checksum: 42f9e82f61314904a81393f6ef75b832c39f39761797250de68c041d8ba4df2ef80db49ab6cd3a292923a6f0f409b8c9980d120f7d32c820b4a8a84a2598a295 + languageName: node + linkType: hard + "string.prototype.padend@npm:@nolyfill/string.prototype.padend@latest": version: 1.0.20 resolution: "@nolyfill/string.prototype.padend@npm:1.0.20" @@ -2113,6 +3086,15 @@ __metadata: languageName: node linkType: hard +"string_decoder@npm:^1.1.1": + version: 1.3.0 + resolution: "string_decoder@npm:1.3.0" + dependencies: + safe-buffer: ~5.2.0 + checksum: 8417646695a66e73aefc4420eb3b84cc9ffd89572861fe004e6aeb13c7bc00e2f616247505d2dbbef24247c372f70268f594af7126f43548565c68c117bdeb56 + languageName: node + linkType: hard + "strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": version: 6.0.1 resolution: "strip-ansi@npm:6.0.1" @@ -2122,7 +3104,7 @@ __metadata: languageName: node linkType: hard -"strip-ansi@npm:^7.0.1": +"strip-ansi@npm:^7.0.1, strip-ansi@npm:^7.1.0": version: 7.1.0 resolution: "strip-ansi@npm:7.1.0" dependencies: @@ -2145,6 +3127,18 @@ __metadata: languageName: node linkType: hard +"supertap@npm:^3.0.1": + version: 3.0.1 + resolution: "supertap@npm:3.0.1" + dependencies: + indent-string: ^5.0.0 + js-yaml: ^3.14.1 + serialize-error: ^7.0.1 + strip-ansi: ^7.0.1 + checksum: ee3d71c1d25f7f15d4a849e72b0c5f430df7cd8f702cf082fdbec5642a9546be6557766745655fa3a3e9c88f7c7eed849f2d74457b5b72cb9d94a779c0c8a948 + languageName: node + linkType: hard + "supports-color@npm:^5.3.0": version: 5.5.0 resolution: "supports-color@npm:5.5.0" @@ -2184,6 +3178,13 @@ __metadata: languageName: node linkType: hard +"temp-dir@npm:^3.0.0": + version: 3.0.0 + resolution: "temp-dir@npm:3.0.0" + checksum: 577211e995d1d584dd60f1469351d45e8a5b4524e4a9e42d3bdd12cfde1d0bb8f5898311bef24e02aaafb69514c1feb58c7b4c33dcec7129da3b0861a4ca935b + languageName: node + linkType: hard + "test-exclude@npm:^6.0.0": version: 6.0.0 resolution: "test-exclude@npm:6.0.0" @@ -2195,6 +3196,13 @@ __metadata: languageName: node linkType: hard +"time-zone@npm:^1.0.0": + version: 1.0.0 + resolution: "time-zone@npm:1.0.0" + checksum: e46f5a69b8c236dcd8e91e29d40d4e7a3495ed4f59888c3f84ce1d9678e20461421a6ba41233509d47dd94bc18f1a4377764838b21b584663f942b3426dcbce8 + languageName: node + linkType: hard + "to-regex-range@npm:^5.0.1": version: 5.0.1 resolution: "to-regex-range@npm:5.0.1" @@ -2204,6 +3212,13 @@ __metadata: languageName: node linkType: hard +"tr46@npm:~0.0.3": + version: 0.0.3 + resolution: "tr46@npm:0.0.3" + checksum: 726321c5eaf41b5002e17ffbd1fb7245999a073e8979085dacd47c4b4e8068ff5777142fc6726d6ca1fd2ff16921b48788b87225cbc57c72636f6efa8efbffe3 + languageName: node + linkType: hard + "tsx@npm:^4.16.2": version: 4.16.2 resolution: "tsx@npm:4.16.2" @@ -2220,6 +3235,13 @@ __metadata: languageName: node linkType: hard +"type-fest@npm:^0.13.1": + version: 0.13.1 + resolution: "type-fest@npm:0.13.1" + checksum: e6bf2e3c449f27d4ef5d56faf8b86feafbc3aec3025fc9a5fbe2db0a2587c44714521f9c30d8516a833c8c506d6263f5cc11267522b10c6ccdb6cc55b0a9d1c4 + languageName: node + linkType: hard + "type-fest@npm:^1.0.2": version: 1.4.0 resolution: "type-fest@npm:1.4.0" @@ -2247,6 +3269,13 @@ __metadata: languageName: node linkType: hard +"unicorn-magic@npm:^0.1.0": + version: 0.1.0 + resolution: "unicorn-magic@npm:0.1.0" + checksum: 48c5882ca3378f380318c0b4eb1d73b7e3c5b728859b060276e0a490051d4180966beeb48962d850fd0c6816543bcdfc28629dcd030bb62a286a2ae2acb5acb6 + languageName: node + linkType: hard + "unique-filename@npm:^3.0.0": version: 3.0.0 resolution: "unique-filename@npm:3.0.0" @@ -2265,6 +3294,13 @@ __metadata: languageName: node linkType: hard +"util-deprecate@npm:^1.0.1": + version: 1.0.2 + resolution: "util-deprecate@npm:1.0.2" + checksum: 474acf1146cb2701fe3b074892217553dfcf9a031280919ba1b8d651a068c9b15d863b7303cb15bd00a862b498e6cf4ad7b4a08fb134edd5a6f7641681cb54a2 + languageName: node + linkType: hard + "v8-to-istanbul@npm:^9.0.0": version: 9.1.0 resolution: "v8-to-istanbul@npm:9.1.0" @@ -2286,6 +3322,30 @@ __metadata: languageName: node linkType: hard +"webidl-conversions@npm:^3.0.0": + version: 3.0.1 + resolution: "webidl-conversions@npm:3.0.1" + checksum: c92a0a6ab95314bde9c32e1d0a6dfac83b578f8fa5f21e675bc2706ed6981bc26b7eb7e6a1fab158e5ce4adf9caa4a0aee49a52505d4d13c7be545f15021b17c + languageName: node + linkType: hard + +"well-known-symbols@npm:^2.0.0": + version: 2.0.0 + resolution: "well-known-symbols@npm:2.0.0" + checksum: 4f54bbc3012371cb4d228f436891b8e7536d34ac61a57541890257e96788608e096231e0121ac24d08ef2f908b3eb2dc0adba35023eaeb2a7df655da91415402 + languageName: node + linkType: hard + +"whatwg-url@npm:^5.0.0": + version: 5.0.0 + resolution: "whatwg-url@npm:5.0.0" + dependencies: + tr46: ~0.0.3 + webidl-conversions: ^3.0.0 + checksum: b8daed4ad3356cc4899048a15b2c143a9aed0dfae1f611ebd55073310c7b910f522ad75d727346ad64203d7e6c79ef25eafd465f4d12775ca44b90fa82ed9e2c + languageName: node + linkType: hard + "which@npm:^1.2.9": version: 1.3.1 resolution: "which@npm:1.3.1" @@ -2319,6 +3379,15 @@ __metadata: languageName: node linkType: hard +"wide-align@npm:^1.1.2": + version: 1.1.5 + resolution: "wide-align@npm:1.1.5" + dependencies: + string-width: ^1.0.2 || 2 || 3 || 4 + checksum: d5fc37cd561f9daee3c80e03b92ed3e84d80dde3365a8767263d03dacfc8fa06b065ffe1df00d8c2a09f731482fcacae745abfbb478d4af36d0a891fad4834d3 + languageName: node + linkType: hard + "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0, wrap-ansi@npm:^7.0.0": version: 7.0.0 resolution: "wrap-ansi@npm:7.0.0" @@ -2348,6 +3417,16 @@ __metadata: languageName: node linkType: hard +"write-file-atomic@npm:^5.0.1": + version: 5.0.1 + resolution: "write-file-atomic@npm:5.0.1" + dependencies: + imurmurhash: ^0.1.4 + signal-exit: ^4.0.1 + checksum: 8dbb0e2512c2f72ccc20ccedab9986c7d02d04039ed6e8780c987dc4940b793339c50172a1008eed7747001bfacc0ca47562668a069a7506c46c77d7ba3926a9 + languageName: node + linkType: hard + "y-protocols@npm:^1.0.6": version: 1.0.6 resolution: "y-protocols@npm:1.0.6" From ddd2ca5a58d38fb8ff3d038a4ac653e896806652 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Fri, 5 Jul 2024 17:28:02 +0800 Subject: [PATCH 16/75] fix: make map.insert's behaver same as yjs --- y-octo-node/index.d.ts | 11 +++++++++-- y-octo-node/index.js | 5 ++++- y-octo-node/src/array.rs | 12 +++++++----- y-octo-node/src/utils.rs | 1 - 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/y-octo-node/index.d.ts b/y-octo-node/index.d.ts index aa909f8..ef8f2f4 100644 --- a/y-octo-node/index.d.ts +++ b/y-octo-node/index.d.ts @@ -5,6 +5,7 @@ export declare function encodeStateAsUpdate(doc: Doc, state?: Buffer | undefined | null): Buffer export declare function encodeStateVector(doc: Doc): Buffer +export declare function compareStructStores(store: YStore, other: YStore): boolean export declare function createDeleteSetFromStructStore(store: YStore): YDeleteSet export declare function equalDeleteSets(a: YDeleteSet, b: YDeleteSet): boolean export declare function snapshot(doc: Doc): YSnapshot @@ -13,7 +14,6 @@ export declare function applyUpdate(doc: Doc, update: Buffer): void export declare function mergeUpdates(updates: Array): Buffer export declare function isAbstractType(unknown: unknown): boolean export class YArray { - constructor() get length(): number get isEmpty(): boolean get(index: number): T @@ -53,16 +53,23 @@ export class Doc { transact(callback: (...args: any[]) => any): void } export class YMap { - constructor() get length(): number get isEmpty(): boolean get(key: string): T set(key: string, value: YArray | YMap | YText | boolean | number | string | Record | null | undefined): void remove(key: string): void toJson(): object + entries(): YMapEntriesIterator + keys(): YMapKeyIterator observe(callback: (...args: any[]) => any): void observeDeep(callback: (...args: any[]) => any): void } +export class YMapEntriesIterator { + [Symbol.iterator](): Iterator +} +export class YMapKeyIterator { + [Symbol.iterator](): Iterator +} export class YText { constructor() get len(): number diff --git a/y-octo-node/index.js b/y-octo-node/index.js index ea5fb9b..00076bc 100644 --- a/y-octo-node/index.js +++ b/y-octo-node/index.js @@ -252,13 +252,14 @@ if (!nativeBinding) { throw new Error(`Failed to load native binding`) } -const { YArray, YArrayIterator, Doc, encodeStateAsUpdate, encodeStateVector, createDeleteSetFromStructStore, equalDeleteSets, snapshot, encodeSnapshot, applyUpdate, mergeUpdates, isAbstractType, YMap, YText, Store, DeleteSet, YSnapshot } = nativeBinding +const { YArray, YArrayIterator, Doc, encodeStateAsUpdate, encodeStateVector, compareStructStores, createDeleteSetFromStructStore, equalDeleteSets, snapshot, encodeSnapshot, applyUpdate, mergeUpdates, isAbstractType, YMap, YMapEntriesIterator, YMapKeyIterator, YText, Store, DeleteSet, YSnapshot } = nativeBinding module.exports.YArray = YArray module.exports.YArrayIterator = YArrayIterator module.exports.Doc = Doc module.exports.encodeStateAsUpdate = encodeStateAsUpdate module.exports.encodeStateVector = encodeStateVector +module.exports.compareStructStores = compareStructStores module.exports.createDeleteSetFromStructStore = createDeleteSetFromStructStore module.exports.equalDeleteSets = equalDeleteSets module.exports.snapshot = snapshot @@ -267,6 +268,8 @@ module.exports.applyUpdate = applyUpdate module.exports.mergeUpdates = mergeUpdates module.exports.isAbstractType = isAbstractType module.exports.YMap = YMap +module.exports.YMapEntriesIterator = YMapEntriesIterator +module.exports.YMapKeyIterator = YMapKeyIterator module.exports.YText = YText module.exports.Store = Store module.exports.DeleteSet = DeleteSet diff --git a/y-octo-node/src/array.rs b/y-octo-node/src/array.rs index 4c4fb52..3c087d5 100644 --- a/y-octo-node/src/array.rs +++ b/y-octo-node/src/array.rs @@ -1,4 +1,8 @@ -use napi::{bindgen_prelude::Array as JsArray, iterator::Generator, Env, JsFunction, JsUnknown, ValueType}; +use napi::{ + bindgen_prelude::{Array as JsArray, FromNapiValue}, + iterator::Generator, + Env, JsFunction, JsUnknown, ValueType, +}; use y_octo::{Any, Array, Value}; use super::*; @@ -122,10 +126,8 @@ impl YArray { { Ok((object, length)) => { for i in 0..length { - if let Ok(any) = object.get_element::(i).and_then(get_any_from_js_unknown) { - self.array - .insert(index as u64 + i as u64, Value::Any(any)) - .map_err(anyhow::Error::from)?; + if let Ok(unknown) = object.get_element::(i) { + self.insert(index + i as i64, MixedRefYType::from_unknown(unknown)?)?; } } Ok(()) diff --git a/y-octo-node/src/utils.rs b/y-octo-node/src/utils.rs index a32efc9..366fb74 100644 --- a/y-octo-node/src/utils.rs +++ b/y-octo-node/src/utils.rs @@ -62,7 +62,6 @@ pub fn get_any_from_js_object(object: JsObject) -> Result { .and_then(|obj| obj.into_utf8().and_then(|s| s.as_str().map(|s| (obj, s.to_string())))) }) { if let Ok(value) = object.get_property::<_, JsUnknown>(obj) { - println!("key: {}", key); map.insert(key, get_any_from_js_unknown(value)?); } } From dff610c898a5132f941de2d4bf2929c6830a7a8d Mon Sep 17 00:00:00 2001 From: DarkSky Date: Mon, 8 Jul 2024 11:50:21 +0800 Subject: [PATCH 17/75] chore: copy new test from yjs --- .../yjs/{yarray.spec.ts => y-array.spec.ts} | 0 y-octo-node/tests/yjs/y-map.spec.ts | 755 +++++ y-octo-node/tests/yjs/y-text.spec.ts | 2635 +++++++++++++++++ 3 files changed, 3390 insertions(+) rename y-octo-node/tests/yjs/{yarray.spec.ts => y-array.spec.ts} (100%) create mode 100644 y-octo-node/tests/yjs/y-map.spec.ts create mode 100644 y-octo-node/tests/yjs/y-text.spec.ts diff --git a/y-octo-node/tests/yjs/yarray.spec.ts b/y-octo-node/tests/yjs/y-array.spec.ts similarity index 100% rename from y-octo-node/tests/yjs/yarray.spec.ts rename to y-octo-node/tests/yjs/y-array.spec.ts diff --git a/y-octo-node/tests/yjs/y-map.spec.ts b/y-octo-node/tests/yjs/y-map.spec.ts new file mode 100644 index 0000000..7710fd5 --- /dev/null +++ b/y-octo-node/tests/yjs/y-map.spec.ts @@ -0,0 +1,755 @@ +import assert, { deepEqual } from "node:assert"; +import { randomInt } from "node:crypto"; +import test from "ava"; + +import { init, compare, applyRandomTests } from "./testHelper.js"; + +import * as Y from "../../yocto"; +import * as prng from "lib0/prng"; + +const production = false; + +let gen: prng.PRNG; +test.beforeEach(() => { + gen = prng.create(randomInt(0, 0xffffffff)); +}); + +test.skip("testIterators", (t) => { + const ydoc = new Y.Doc(); + const ymap = ydoc.createMap(); + // we are only checking if the type assumptions are correct + const vals = Array.from(ymap.values()); + const entries = Array.from(ymap.entries()); + const keys = Array.from(ymap.keys()); + console.log(vals, entries, keys); +}); + +/** + * Computing event changes after transaction should result in an error. See yjs#539 + */ +test.skip("testMapEventError", (t) => { + const doc = new Y.Doc(); + const ymap = doc.createMap(); + /** + * @type {any} + */ + let event: any = null; + ymap.observe((e) => { + event = e; + }); + t.fails(() => { + t.info(event.keys); + }); + t.fails(() => { + t.info(event.keys); + }); +}); + +test.skip("testMapHavingIterableAsConstructorParamTests", (t) => { + const { map0 } = init(gen, { users: 1 }); + + const m1 = new Y.Map(Object.entries({ number: 1, string: "hello" })); + map0.set("m1", m1); + t.assert(m1.get("number") === 1); + t.assert(m1.get("string") === "hello"); + + const m2 = new Y.Map([ + ["object", { x: 1 }], + ["boolean", true], + ]); + map0.set("m2", m2); + t.assert(m2.get("object").x === 1); + t.assert(m2.get("boolean") === true); + + const m3 = new Y.Map([...m1, ...m2]); + map0.set("m3", m3); + t.assert(m3.get("number") === 1); + t.assert(m3.get("string") === "hello"); + t.assert(m3.get("object").x === 1); + t.assert(m3.get("boolean") === true); +}); + +test.skip("testBasicMapTests", (t) => { + const { testConnector, users, map0, map1, map2 } = init(gen, { users: 3 }); + assert(map0); + assert(map1); + assert(map2); + + users[2].disconnect(); + + map0.set("null", null); + map0.set("number", 1); + map0.set("string", "hello Y"); + map0.set("object", { key: { key2: "value" } }); + map0.set("y-map", new Y.Map()); + map0.set("boolean1", true); + map0.set("boolean0", false); + const map = map0.get("y-map"); + map.set("y-array", new Y.Array()); + const array = map.get("y-array"); + array.insert(0, [0]); + array.insert(0, [-1]); + + t.assert(map0.get("null") === null, "client 0 computed the change (null)"); + t.assert(map0.get("number") === 1, "client 0 computed the change (number)"); + t.assert( + map0.get("string") === "hello Y", + "client 0 computed the change (string)", + ); + t.assert( + map0.get("boolean0") === false, + "client 0 computed the change (boolean)", + ); + t.assert( + map0.get("boolean1") === true, + "client 0 computed the change (boolean)", + ); + t.deepEqual( + map0.get("object"), + { key: { key2: "value" } }, + "client 0 computed the change (object)", + ); + t.assert( + map0.get("y-map").get("y-array").get(0) === -1, + "client 0 computed the change (type)", + ); + t.assert(map0.size === 7, "client 0 map has correct size"); + + users[2].connect(); + testConnector.flushAllMessages(); + + t.assert(map1.get("null") === null, "client 1 received the update (null)"); + t.assert(map1.get("number") === 1, "client 1 received the update (number)"); + t.assert( + map1.get("string") === "hello Y", + "client 1 received the update (string)", + ); + t.assert( + map1.get("boolean0") === false, + "client 1 computed the change (boolean)", + ); + t.assert( + map1.get("boolean1") === true, + "client 1 computed the change (boolean)", + ); + t.deepEqual( + map1.get("object"), + { key: { key2: "value" } }, + "client 1 received the update (object)", + ); + t.assert( + map1.get("y-map").get("y-array").get(0) === -1, + "client 1 received the update (type)", + ); + t.assert(map1.size === 7, "client 1 map has correct size"); + + // compare disconnected user + t.assert( + map2.get("null") === null, + "client 2 received the update (null) - was disconnected", + ); + t.assert( + map2.get("number") === 1, + "client 2 received the update (number) - was disconnected", + ); + t.assert( + map2.get("string") === "hello Y", + "client 2 received the update (string) - was disconnected", + ); + t.assert( + map2.get("boolean0") === false, + "client 2 computed the change (boolean)", + ); + t.assert( + map2.get("boolean1") === true, + "client 2 computed the change (boolean)", + ); + t.deepEqual( + map2.get("object"), + { key: { key2: "value" } }, + "client 2 received the update (object) - was disconnected", + ); + t.assert( + map2.get("y-map").get("y-array").get(0) === -1, + "client 2 received the update (type) - was disconnected", + ); + compare(users); +}); + +test.skip("testGetAndSetOfMapProperty", (t) => { + const { testConnector, users, map0 } = init(gen, { users: 2 }); + assert(map0); + + map0.set("stuff", "stuffy"); + map0.set("undefined", undefined); + map0.set("null", null); + t.deepEqual(map0.get("stuff"), "stuffy"); + + testConnector.flushAllMessages(); + + for (const user of users) { + const u = user.getOrCreateMap("map"); + t.deepEqual(u.get("stuff"), "stuffy"); + t.assert(u.get("undefined") === undefined, "undefined"); + t.deepEqual(u.get("null"), null, "null"); + } + compare(users); +}); + +test.skip("testYmapSetsYmap", (t) => { + const { users, map0 } = init(gen, { users: 2 }); + assert(map0); + + const map = map0.set("Map", new Y.Map()); + t.assert(map0.get("Map") === map); + map.set("one", 1); + t.deepEqual(map.get("one"), 1); + compare(users); +}); + +test.skip("testYmapSetsYarray", (t) => { + const { users, map0 } = init(gen, { users: 2 }); + assert(map0); + + const array = map0.set("Array", new Y.Array()); + t.assert(array === map0.get("Array")); + array.insert(0, [1, 2, 3]); + // @ts-ignore + t.deepEqual(map0.toJSON(), { Array: [1, 2, 3] }); + compare(users); +}); + +test.skip("testGetAndSetOfMapPropertySyncs", (t) => { + const { testConnector, users, map0 } = init(gen, { users: 2 }); + assert(map0); + + map0.set("stuff", "stuffy"); + t.deepEqual(map0.get("stuff"), "stuffy"); + testConnector.flushAllMessages(); + for (const user of users) { + const u = user.getOrCreateMap("map"); + t.deepEqual(u.get("stuff"), "stuffy"); + } + compare(users); +}); + +test.skip("testGetAndSetOfMapPropertyWithConflict", (t) => { + const { testConnector, users, map0, map1 } = init(gen, { users: 3 }); + assert(map0); + assert(map1); + + map0.set("stuff", "c0"); + map1.set("stuff", "c1"); + testConnector.flushAllMessages(); + for (const user of users) { + const u = user.getOrCreateMap("map"); + t.deepEqual(u.get("stuff"), "c1"); + } + compare(users); +}); + +test.skip("testSizeAndDeleteOfMapProperty", (t) => { + const { map0 } = init(gen, { users: 1 }); + assert(map0); + + map0.set("stuff", "c0"); + map0.set("otherstuff", "c1"); + t.assert(map0.size === 2, `map size is ${map0.size} expected 2`); + map0.delete("stuff"); + t.assert( + map0.size === 1, + `map size after delete is ${map0.size}, expected 1`, + ); + map0.delete("otherstuff"); + t.assert( + map0.size === 0, + `map size after delete is ${map0.size}, expected 0`, + ); +}); + +test.skip("testGetAndSetAndDeleteOfMapProperty", (t) => { + const { testConnector, users, map0, map1 } = init(gen, { users: 3 }); + assert(map0); + assert(map1); + + map0.set("stuff", "c0"); + map1.set("stuff", "c1"); + map1.delete("stuff"); + testConnector.flushAllMessages(); + for (const user of users) { + const u = user.getOrCreateMap("map"); + t.assert(u.get("stuff") === undefined); + } + compare(users); +}); + +test.skip("testSetAndClearOfMapProperties", (t) => { + const { testConnector, users, map0 } = init(gen, { users: 1 }); + assert(map0); + + map0.set("stuff", "c0"); + map0.set("otherstuff", "c1"); + map0.clear(); + testConnector.flushAllMessages(); + for (const user of users) { + const u = user.getOrCreateMap("map"); + t.assert(u.get("stuff") === undefined); + t.assert(u.get("otherstuff") === undefined); + t.assert(u.size === 0, `map size after clear is ${u.size}, expected 0`); + } + compare(users); +}); + +test.skip("testSetAndClearOfMapPropertiesWithConflicts", (t) => { + const { testConnector, users, map0, map1, map2, map3 } = init(gen, { + users: 4, + }); + assert(map0); + assert(map1); + assert(map2); + assert(map3); + + map0.set("stuff", "c0"); + map1.set("stuff", "c1"); + map1.set("stuff", "c2"); + map2.set("stuff", "c3"); + testConnector.flushAllMessages(); + map0.set("otherstuff", "c0"); + map1.set("otherstuff", "c1"); + map2.set("otherstuff", "c2"); + map3.set("otherstuff", "c3"); + map3.clear(); + testConnector.flushAllMessages(); + for (const user of users) { + const u = user.getOrCreateMap("map"); + t.assert(u.get("stuff") === undefined); + t.assert(u.get("otherstuff") === undefined); + t.assert(u.size === 0, `map size after clear is ${u.size}, expected 0`); + } + compare(users); +}); + +test.skip("testGetAndSetOfMapPropertyWithThreeConflicts", (t) => { + const { testConnector, users, map0, map1, map2 } = init(gen, { users: 3 }); + assert(map0); + assert(map1); + assert(map2); + + map0.set("stuff", "c0"); + map1.set("stuff", "c1"); + map1.set("stuff", "c2"); + map2.set("stuff", "c3"); + testConnector.flushAllMessages(); + for (const user of users) { + const u = user.getOrCreateMap("map"); + t.deepEqual(u.get("stuff"), "c3"); + } + compare(users); +}); + +test.skip("testGetAndSetAndDeleteOfMapPropertyWithThreeConflicts", (t) => { + const { testConnector, users, map0, map1, map2, map3 } = init(gen, { + users: 4, + }); + assert(map0); + assert(map1); + assert(map2); + assert(map3); + + map0.set("stuff", "c0"); + map1.set("stuff", "c1"); + map1.set("stuff", "c2"); + map2.set("stuff", "c3"); + testConnector.flushAllMessages(); + map0.set("stuff", "deleteme"); + map1.set("stuff", "c1"); + map2.set("stuff", "c2"); + map3.set("stuff", "c3"); + map3.delete("stuff"); + testConnector.flushAllMessages(); + for (const user of users) { + const u = user.getOrCreateMap("map"); + t.assert(u.get("stuff") === undefined); + } + compare(users); +}); + +test.skip("testObserveDeepProperties", (t) => { + const { testConnector, users, map1, map2, map3 } = init(gen, { users: 4 }); + assert(map1); + assert(map2); + assert(map3); + + const _map1 = map1.set("map", new Y.Map()); + let calls = 0; + let dmapid; + map1.observeDeep((events) => { + events.forEach((event) => { + calls++; + // @ts-ignore + t.assert(event.keysChanged.has("deepmap")); + t.assert(event.path.length === 1); + t.assert(event.path[0] === "map"); + // @ts-ignore + dmapid = event.target.get("deepmap")._item.id; + }); + }); + testConnector.flushAllMessages(); + const _map3 = map3.get("map"); + _map3.set("deepmap", new Y.Map()); + testConnector.flushAllMessages(); + const _map2 = map2.get("map"); + _map2.set("deepmap", new Y.Map()); + testConnector.flushAllMessages(); + const dmap1 = _map1.get("deepmap"); + const dmap2 = _map2.get("deepmap"); + const dmap3 = _map3.get("deepmap"); + t.assert(calls > 0); + t.assert(compareIDs(dmap1._item.id, dmap2._item.id)); + t.assert(compareIDs(dmap1._item.id, dmap3._item.id)); + // @ts-ignore we want the possibility of dmapid being undefined + t.assert(compareIDs(dmap1._item.id, dmapid)); + compare(users); +}); + +test.skip("testObserversUsingObservedeep", (t) => { + const { users, map0 } = init(gen, { users: 2 }); + assert(map0); + + const pathes: Array> = []; + let calls = 0; + map0.observeDeep((events) => { + events.forEach((event) => { + pathes.push(event.path); + }); + calls++; + }); + map0.set("map", new Y.Map()); + map0.get("map").set("array", new Y.Array()); + map0.get("map").get("array").insert(0, ["content"]); + t.assert(calls === 3); + t.deepEqual(pathes, [[], ["map"], ["map", "array"]]); + compare(users); +}); + +test.skip("testPathsOfSiblingEvents", (t) => { + const { users, map0 } = init(gen, { users: 2 }); + assert(map0); + + const pathes: Array> = []; + 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) => { + pathes.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.deepEqual(pathes, [["map"], ["map", "text1"]]); + compare(users); +}); + +// TODO: Test events in Y.Map +/** + * @param {Object} is + * @param {Object} should + */ +const compareEvent = ( + is: { [s: string]: any }, + should: { [s: string]: any }, +) => { + for (const key in should) { + deepEqual(should[key], is[key]); + } +}; + +test.skip("testThrowsAddAndUpdateAndDeleteEvents", (t) => { + const { users, map0 } = init(gen, { users: 2 }); + /** + * @type {Object} + */ + let event: { [s: string]: any } = {}; + map0.observe((e) => { + event = e; // just put it on event, should be thrown synchronously anyway + }); + map0.set("stuff", 4); + compareEvent(event, { + target: map0, + keysChanged: new Set(["stuff"]), + }); + // update, oldValue is in contents + map0.set("stuff", new Y.Array()); + compareEvent(event, { + target: map0, + keysChanged: new Set(["stuff"]), + }); + // update, oldValue is in opContents + map0.set("stuff", 5); + // delete + map0.delete("stuff"); + compareEvent(event, { + keysChanged: new Set(["stuff"]), + target: map0, + }); + compare(users); +}); + +test.skip("testThrowsDeleteEventsOnClear", (t) => { + const { users, map0 } = init(gen, { users: 2 }); + /** + * @type {Object} + */ + let event: { [s: string]: any } = {}; + map0.observe((e) => { + event = e; // just put it on event, should be thrown synchronously anyway + }); + // set values + map0.set("stuff", 4); + map0.set("otherstuff", new Y.Array()); + // clear + map0.clear(); + compareEvent(event, { + keysChanged: new Set(["stuff", "otherstuff"]), + target: map0, + }); + compare(users); +}); + +test.skip("testChangeEvent", (t) => { + const { map0, users } = init(gen, { users: 2 }); + /** + * @type {any} + */ + let changes: any = null; + /** + * @type {any} + */ + let keyChange: any = null; + map0.observe((e) => { + changes = e.changes; + }); + map0.set("a", 1); + keyChange = changes.keys.get("a"); + t.assert( + changes !== null && + keyChange.action === "add" && + keyChange.oldValue === undefined, + ); + map0.set("a", 2); + keyChange = changes.keys.get("a"); + t.assert( + changes !== null && + keyChange.action === "update" && + keyChange.oldValue === 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, + ); + 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, + ); + users[0].transact(() => { + map0.set("c", 1); + map0.delete("c"); + }); + t.assert(changes !== null && changes.keys.size === 0); + 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, + ); + compare(users); +}); + +test.skip("testYmapEventExceptionsShouldCompleteTransaction", (t) => { + const doc = new Y.Doc(); + const map = doc.getOrCreateMap("map"); + + let updateCalled = false; + let throwingObserverCalled = false; + let throwingDeepObserverCalled = false; + doc.on("update", () => { + updateCalled = true; + }); + + const throwingObserver = () => { + throwingObserverCalled = true; + throw new Error("Failure"); + }; + + const throwingDeepObserver = () => { + throwingDeepObserverCalled = true; + throw new Error("Failure"); + }; + + map.observe(throwingObserver); + map.observeDeep(throwingDeepObserver); + + t.throws(() => { + map.set("y", "2"); + }); + + t.assert(updateCalled); + t.assert(throwingObserverCalled); + t.assert(throwingDeepObserverCalled); + + // check if it works again + updateCalled = false; + throwingObserverCalled = false; + throwingDeepObserverCalled = false; + t.throws(() => { + map.set("z", "3"); + }); + + t.assert(updateCalled); + t.assert(throwingObserverCalled); + t.assert(throwingDeepObserverCalled); + + t.assert(map.get("z") === "3"); +}); + +test.skip("testYmapEventHasCorrectValueWhenSettingAPrimitive", (t) => { + const { users, map0 } = init(gen, { users: 3 }); + assert(map0); + + /** + * @type {Object} + */ + let event: { [s: string]: any } = {}; + map0.observe((e) => { + event = e; + }); + map0.set("stuff", 2); + t.deepEqual(event.value, event.target.get(event.name)); + compare(users); +}); + +test.skip("testYmapEventHasCorrectValueWhenSettingAPrimitiveFromOtherUser", (t) => { + const { users, map0, map1, testConnector } = init(gen, { users: 3 }); + assert(map0); + assert(map1); + + let event: Record = {}; + map0.observe((e) => { + event = e; + }); + map1.set("stuff", 2); + testConnector.flushAllMessages(); + t.deepEqual(event.value, event.target.get(event.name)); + compare(users); +}); + +const mapTransactions: Array<(arg0: Y.Doc, arg1: prng.PRNG) => void> = [ + function set(user, gen) { + const key = prng.oneOf(gen, ["one", "two"]); + const value = prng.utf16String(gen); + user.getOrCreateMap("map").set(key, value); + }, + function setType(user, gen) { + const key = prng.oneOf(gen, ["one", "two"]); + const type = prng.oneOf(gen, [new Y.Array(), new Y.Map()]); + user.getOrCreateMap("map").set(key, type); + if (type instanceof Y.Array) { + type.insert(0, [1, 2, 3, 4]); + } else { + type.set("deepkey", "deepvalue"); + } + }, + function _delete(user, gen) { + const key = prng.oneOf(gen, ["one", "two"]); + user.getOrCreateMap("map").delete(key); + }, +]; + +test.skip("testRepeatGeneratingYmapTests10", (t) => { + applyRandomTests(tc, mapTransactions, 3); +}); + +test.skip("testRepeatGeneratingYmapTests40", (t) => { + applyRandomTests(tc, mapTransactions, 40); +}); + +test.skip("testRepeatGeneratingYmapTests42", (t) => { + applyRandomTests(tc, mapTransactions, 42); +}); + +test.skip("testRepeatGeneratingYmapTests43", (t) => { + applyRandomTests(tc, mapTransactions, 43); +}); + +test.skip("testRepeatGeneratingYmapTests44", (t) => { + applyRandomTests(tc, mapTransactions, 44); +}); + +test.skip("testRepeatGeneratingYmapTests45", (t) => { + applyRandomTests(tc, mapTransactions, 45); +}); + +test.skip("testRepeatGeneratingYmapTests46", (t) => { + applyRandomTests(tc, mapTransactions, 46); +}); + +test.skip("testRepeatGeneratingYmapTests300", (t) => { + applyRandomTests(tc, mapTransactions, 300); +}); + +test.skip("testRepeatGeneratingYmapTests400", (t) => { + applyRandomTests(tc, mapTransactions, 400); +}); + +test.skip("testRepeatGeneratingYmapTests500", (t) => { + applyRandomTests(tc, mapTransactions, 500); +}); + +test.skip("testRepeatGeneratingYmapTests600", (t) => { + applyRandomTests(tc, mapTransactions, 600); +}); + +test.skip("testRepeatGeneratingYmapTests1000", (t) => { + applyRandomTests(tc, mapTransactions, 1000); +}); + +test.skip("testRepeatGeneratingYmapTests1800", (t) => { + applyRandomTests(tc, mapTransactions, 1800); +}); + +test.skip("testRepeatGeneratingYmapTests5000", (t) => { + if (!production) return; + applyRandomTests(tc, mapTransactions, 5000); +}); + +test.skip("testRepeatGeneratingYmapTests10000", (t) => { + if (!production) return; + applyRandomTests(tc, mapTransactions, 10000); +}); + +test.skip("testRepeatGeneratingYmapTests100000", (t) => { + if (!production) return; + applyRandomTests(tc, mapTransactions, 100000); +}); diff --git a/y-octo-node/tests/yjs/y-text.spec.ts b/y-octo-node/tests/yjs/y-text.spec.ts new file mode 100644 index 0000000..94361f0 --- /dev/null +++ b/y-octo-node/tests/yjs/y-text.spec.ts @@ -0,0 +1,2635 @@ +import assert, { deepEqual } from "node:assert"; +import { randomInt } from "node:crypto"; +import test from "ava"; + +import * as prng from "lib0/prng"; +import * as math from "lib0/math"; + +import * as Y from "../../yocto"; +import { applyRandomTests, compare, init } from "./testHelper"; + +let gen: prng.PRNG; +test.beforeEach(() => { + gen = prng.create(randomInt(0, 0xffffffff)); +}); + +/** + * https://github.com/yjs/yjs/issues/474 + * @todo Remove debug: 127.0.0.1:8080/test.html?filter=\[88/ + */ +test.skip("testDeltaBug", (t) => { + const initialDelta = [ + { + attributes: { + "block-id": "block-28eea923-9cbb-4b6f-a950-cf7fd82bc087", + }, + insert: "\n", + }, + { + attributes: { + "table-col": { + width: "150", + }, + }, + insert: "\n\n\n", + }, + { + attributes: { + "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", + }, + { + attributes: { + "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", + }, + { + attributes: { + "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", + }, + { + attributes: { + "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", + }, + { + attributes: { + "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", + }, + { + attributes: { + "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", + }, + { + attributes: { + "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", + }, + { + attributes: { + "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", + }, + { + attributes: { + "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", + }, + { + attributes: { + "block-id": "block-38161f9c-6f6d-44c5-b086-54cc6490f1e3", + }, + insert: "\n", + }, + { + insert: "Content after table", + }, + { + attributes: { + "block-id": "block-15630542-ef45-412d-9415-88f0052238ce", + }, + insert: "\n", + }, + ]; + const ydoc1 = new Y.Doc(); + const ytext = ydoc1.createText(); + ytext.applyDelta(initialDelta); + const addingDash = [ + { + retain: 12, + }, + { + insert: "-", + }, + ]; + ytext.applyDelta(addingDash); + const addingSpace = [ + { + retain: 13, + }, + { + insert: " ", + }, + ]; + ytext.applyDelta(addingSpace); + const addingList = [ + { + retain: 12, + }, + { + delete: 2, + }, + { + retain: 1, + attributes: { + // 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.toDelta(); + const expectedResult = [ + { + attributes: { + "block-id": "block-28eea923-9cbb-4b6f-a950-cf7fd82bc087", + }, + insert: "\n", + }, + { + attributes: { + "table-col": { + width: "150", + }, + }, + insert: "\n\n\n", + }, + { + attributes: { + "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", + }, + { + attributes: { + "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", + }, + { + attributes: { + "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", + }, + { + attributes: { + "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", + }, + { + attributes: { + "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", + }, + { + attributes: { + "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", + }, + { + attributes: { + "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", + }, + { + attributes: { + "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", + }, + { + insert: "\n", + // This attibutes has only list and no table-cell-line + attributes: { + 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 + { + attributes: { + "block-id": "block-38161f9c-6f6d-44c5-b086-54cc6490f1e3", + }, + insert: "\n", + }, + { + insert: "Content after table", + }, + { + attributes: { + "block-id": "block-15630542-ef45-412d-9415-88f0052238ce", + }, + insert: "\n", + }, + ]; + t.deepEqual(result, expectedResult); +}); + +/** + * https://github.com/yjs/yjs/issues/503 + */ +test.skip("testDeltaBug2", (t) => { + const initialContent = [ + { insert: "Thomas' section" }, + { + insert: "\n", + attributes: { "block-id": "block-61ae80ac-a469-4eae-bac9-3b6a2c380118" }, + }, + { + insert: "\n", + attributes: { "block-id": "block-d265d93f-1cc7-40ee-bb58-8270fca2619f" }, + }, + { insert: "123" }, + { + insert: "\n", + attributes: { + "block-id": "block-592a7bee-76a3-4e28-9c25-7a84344f8813", + list: { list: "toggled", "toggle-id": "list-66xfft" }, + }, + }, + { insert: "456" }, + { + insert: "\n", + attributes: { + indent: 1, + "block-id": "block-3ee2bd70-b97f-45b2-9115-f1e8910235b1", + list: { list: "toggled", "toggle-id": "list-6vh0t0" }, + }, + }, + { insert: "789" }, + { + insert: "\n", + attributes: { + indent: 1, + "block-id": "block-78150cf3-9bb5-4dea-a6f5-0ce1d2a98b9c", + list: { list: "toggled", "toggle-id": "list-7jr0l2" }, + }, + }, + { insert: "901" }, + { + insert: "\n", + attributes: { + 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", + attributes: { "block-id": "block-8a1d2bb6-23c2-4bcf-af3c-3919ffea1697" }, + }, + { insert: "\n\n", attributes: { "table-col": { width: "150" } } }, + { + insert: "\n", + attributes: { "table-col": { width: "150" } }, + }, + { + insert: "\n", + attributes: { + "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", + attributes: { + "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", + attributes: { + "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", + attributes: { + "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", + attributes: { + "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", + attributes: { + "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", + attributes: { + "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", + attributes: { + "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", + attributes: { + "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", + attributes: { "block-id": "block-15b4483c-da98-4ded-91d3-c3d6ebc82582" }, + }, + { insert: { divider: true } }, + { + insert: "\n", + attributes: { "block-id": "block-68552c8e-b57b-4f4a-9f36-6cc1ef6b3461" }, + }, + { insert: "jklasjdf" }, + { + insert: "\n", + attributes: { + "block-id": "block-c8b2df7d-8ec5-4dd4-81f1-8d8efc40b1b4", + list: { list: "toggled", "toggle-id": "list-9ss39s" }, + }, + }, + { insert: "asdf" }, + { + insert: "\n", + attributes: { + "block-id": "block-4f252ceb-14da-49ae-8cbd-69a701d18e2a", + list: { list: "toggled", "toggle-id": "list-uvo013" }, + }, + }, + { insert: "adg" }, + { + insert: "\n", + attributes: { + "block-id": "block-ccb9b72e-b94d-45a0-aae4-9b0a1961c533", + list: { list: "toggled", "toggle-id": "list-k53iwe" }, + }, + }, + { insert: "asdfasdfasdf" }, + { + insert: "\n", + attributes: { + "block-id": "block-ccb9b72e-b94d-45a0-aae4-9b0a1961c533", + list: { list: "none" }, + indent: 1, + }, + }, + { insert: "asdf" }, + { + insert: "\n", + attributes: { + "block-id": "block-f406f76d-f338-4261-abe7-5c9131f7f1ad", + list: { list: "toggled", "toggle-id": "list-en86ur" }, + }, + }, + { + insert: "\n", + attributes: { "block-id": "block-be18141c-9b6b-434e-8fd0-2c214437d560" }, + }, + { + insert: "\n", + attributes: { "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", + attributes: { "block-id": "block-9d6566a1-be55-4e20-999a-b990bc15e143" }, + }, + { + insert: "\n", + attributes: { + "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", + attributes: { + "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", + attributes: { + "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", + attributes: { "block-id": "block-2d6ff0f4-ff00-42b7-a8e2-b816463d8fb5" }, + }, + { insert: { divider: true } }, + { + insert: "\n", + attributes: { "table-col": { width: "150" } }, + }, + { insert: "\n", attributes: { "table-col": { width: "154" } } }, + { + insert: "\n", + attributes: { "table-col": { width: "150" } }, + }, + + { + insert: "\n", + attributes: { + "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", + attributes: { + "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", + attributes: { + "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", + attributes: { + "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", + attributes: { + "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", + attributes: { + "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", + attributes: { + "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", + attributes: { + "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", + attributes: { + "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", + attributes: { + "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", + attributes: { + "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", + attributes: { + "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", + attributes: { + "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", + attributes: { + "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", + attributes: { + "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", + attributes: { "block-id": "block-7fa64af0-6974-4205-8cee-529f8bd46852" }, + }, + { insert: { divider: true } }, + { insert: "Brandon's Section" }, + { + insert: "\n", + attributes: { + header: 2, + "block-id": "block-cf49462c-2370-48ff-969d-576cb32c39a1", + }, + }, + { + insert: "\n", + attributes: { "block-id": "block-30ef8361-0dd6-4eee-b4eb-c9012d0e9070" }, + }, + { + insert: { + slash_command: { + id: "doc_94zq-2436", + sessionId: "x9x08o916", + replace: "/", + }, + }, + }, + { + insert: "\n", + attributes: { "block-id": "block-166ed856-cf8c-486a-9365-f499b21d91b3" }, + }, + { insert: { divider: true } }, + { + insert: "\n", + attributes: { + 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", + attributes: { + "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", + attributes: { + "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", + attributes: { + "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", + attributes: { + "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", + attributes: { + "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", + attributes: { + "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", + attributes: { + "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", + attributes: { + "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", + attributes: { "block-id": "block-fbf23cab-1ce9-4ede-9953-f2f8250004cf" }, + }, + { + insert: "\n", + attributes: { "block-id": "block-c3fbb8c9-495c-40b0-b0dd-f6e33dd64b1b" }, + }, + { + insert: "\n", + attributes: { "block-id": "block-3417ad09-92a3-4a43-b5db-6dbcb0f16db4" }, + }, + { + insert: "\n", + attributes: { "block-id": "block-b9eacdce-4ba3-4e66-8b69-3eace5656057" }, + }, + { insert: "Dan Gornstein" }, + { + insert: "\n", + attributes: { + "block-id": "block-d7c6ae0d-a17c-433e-85fd-5efc52b587fb", + header: 1, + }, + }, + { + insert: "\n", + attributes: { "block-id": "block-814521bd-0e14-4fbf-b332-799c6452a624" }, + }, + { insert: "aaa" }, + { + insert: "\n", + attributes: { + "block-id": "block-6aaf4dcf-dc21-45c6-b723-afb25fe0f498", + list: { list: "toggled", "toggle-id": "list-idl93b" }, + }, + }, + { insert: "bb" }, + { + insert: "\n", + attributes: { + indent: 1, + "block-id": "block-3dd75392-fa50-4bfb-ba6b-3b7d6bd3f1a1", + list: { list: "toggled", "toggle-id": "list-mrq7j2" }, + }, + }, + { insert: "ccc" }, + { + insert: "\n", + attributes: { + "block-id": "block-2528578b-ecda-4f74-9fd7-8741d72dc8b3", + indent: 2, + list: { list: "toggled", "toggle-id": "list-liu7dl" }, + }, + }, + { + insert: "\n", + attributes: { "block-id": "block-18bf68c3-9ef3-4874-929c-9b6bb1a00325" }, + }, + { + insert: "\n", + attributes: { "table-col": { width: "150" } }, + }, + { insert: "\n", attributes: { "table-col": { width: "150" } } }, + { + insert: "\n", + attributes: { "table-col": { width: "150" } }, + }, + { + insert: "\n", + attributes: { + "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", + attributes: { + "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", + attributes: { + 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", + attributes: { + 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", + attributes: { + "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", + attributes: { + "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", + attributes: { + "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", + attributes: { + "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", + attributes: { + "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", + attributes: { + "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", + attributes: { + "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", + attributes: { + "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", + attributes: { + "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", + attributes: { "block-id": "block-28f28cb8-51a2-4156-acf9-2380e1349745" }, + }, + { insert: { divider: true } }, + { + insert: "\n", + attributes: { "block-id": "block-a1193252-c0c8-47fe-b9f6-32c8b01a1619" }, + }, + { insert: "\n", attributes: { "table-col": { width: "150" } } }, + { + insert: "\n\n", + attributes: { "table-col": { width: "150" } }, + }, + { insert: "/This is a test." }, + { + insert: "\n", + attributes: { + "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", + attributes: { + "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", + attributes: { + "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", + attributes: { + "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", + attributes: { + "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", + attributes: { + "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", + attributes: { + "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", + attributes: { + "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", + attributes: { + "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", + attributes: { "block-id": "block-21099df0-afb2-4cd3-834d-bb37800eb06a" }, + }, + ]; + const ydoc = new Y.Doc(); + const ytext = ydoc.getOrCreateText("id"); + ytext.applyDelta(initialContent); + const changeEvent = [ + { retain: 90 }, + { delete: 4 }, + { + retain: 1, + attributes: { + layout: null, + "layout-width": null, + "block-id": "block-9d6566a1-be55-4e20-999a-b990bc15e143", + }, + }, + ]; + ytext.applyDelta(changeEvent); + const delta = ytext.toDelta(); + t.deepEqual(delta[41], { + insert: "\n", + attributes: { + "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 + * synced document. + */ +test.skip("testDeltaAfterConcurrentFormatting", (t) => { + const { text0, text1, testConnector } = init(gen, { users: 2 }); + assert(text0); + assert(text1); + text0.insert(0, "abcde"); + testConnector.flushAllMessages(); + text0.format(0, 3, { bold: true }); + text1.format(2, 2, { bold: true }); + /** + * @type {any} + */ + const deltas: any = []; + text1.observe((event) => { + if (event.delta.length > 0) { + deltas.push(event.delta); + } + }); + testConnector.flushAllMessages(); + t.deepEqual(deltas, [ + [ + { retain: 3, attributes: { bold: true } }, + { retain: 2, attributes: { bold: null } }, + ], + ]); +}); + +test.skip("testBasicInsertAndDelete", (t) => { + const { users, text0 } = init(gen, { users: 2 }); + assert(text0); + + let delta; + + text0.observe((event) => { + delta = event.delta; + }); + + text0.delete(0, 0); + t.assert(true, "Does not throw when deleting zero elements with position 0"); + + text0.insert(0, "abc"); + t.assert(text0.toString() === "abc", "Basic insert works"); + t.deepEqual(delta, [{ insert: "abc" }]); + + text0.delete(0, 1); + t.assert(text0.toString() === "bc", "Basic delete works (position 0)"); + t.deepEqual(delta, [{ delete: 1 }]); + + text0.delete(1, 1); + t.assert(text0.toString() === "b", "Basic delete works (position 1)"); + t.deepEqual(delta, [{ retain: 1 }, { delete: 1 }]); + + users[0].transact(() => { + text0.insert(0, "1"); + text0.delete(0, 1); + }); + t.deepEqual(delta, []); + + compare(users); +}); + +test.skip("testBasicFormat", (t) => { + const { users, text0 } = init(gen, { users: 2 }); + assert(text0); + + let delta; + text0.observe((event) => { + delta = event.delta; + }); + text0.insert(0, "abc", { bold: true }); + t.assert(text0.toString() === "abc", "Basic insert with attributes works"); + t.deepEqual(text0.toDelta(), [{ insert: "abc", attributes: { bold: true } }]); + t.deepEqual(delta, [{ insert: "abc", attributes: { bold: true } }]); + text0.delete(0, 1); + t.assert( + text0.toString() === "bc", + "Basic delete on formatted works (position 0)", + ); + t.deepEqual(text0.toDelta(), [{ insert: "bc", attributes: { bold: true } }]); + t.deepEqual(delta, [{ delete: 1 }]); + text0.delete(1, 1); + t.assert(text0.toString() === "b", "Basic delete works (position 1)"); + t.deepEqual(text0.toDelta(), [{ insert: "b", attributes: { bold: true } }]); + t.deepEqual(delta, [{ retain: 1 }, { delete: 1 }]); + text0.insert(0, "z", { bold: true }); + t.assert(text0.toString() === "zb"); + t.deepEqual(text0.toDelta(), [{ insert: "zb", attributes: { bold: true } }]); + t.deepEqual(delta, [{ insert: "z", attributes: { 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.deepEqual(text0.toDelta(), [ + { insert: "y" }, + { insert: "zb", attributes: { bold: true } }, + ]); + t.deepEqual(delta, [{ insert: "y" }]); + text0.format(0, 2, { bold: null }); + t.assert(text0.toString() === "yzb"); + t.deepEqual(text0.toDelta(), [ + { insert: "yz" }, + { insert: "b", attributes: { bold: true } }, + ]); + t.deepEqual(delta, [ + { retain: 1 }, + { retain: 1, attributes: { bold: null } }, + ]); + compare(users); +}); + +test.skip("testFalsyFormats", (t) => { + const { users, text0 } = init(gen, { users: 2 }); + assert(text0); + + let delta; + text0.observe((event) => { + delta = event.delta; + }); + text0.insert(0, "abcde", { falsy: false }); + t.deepEqual(text0.toDelta(), [ + { insert: "abcde", attributes: { falsy: false } }, + ]); + t.deepEqual(delta, [{ insert: "abcde", attributes: { falsy: false } }]); + text0.format(1, 3, { falsy: true }); + t.deepEqual(text0.toDelta(), [ + { insert: "a", attributes: { falsy: false } }, + { insert: "bcd", attributes: { falsy: true } }, + { insert: "e", attributes: { falsy: false } }, + ]); + t.deepEqual(delta, [ + { retain: 1 }, + { retain: 3, attributes: { falsy: true } }, + ]); + text0.format(2, 1, { falsy: false }); + t.deepEqual(text0.toDelta(), [ + { insert: "a", attributes: { falsy: false } }, + { insert: "b", attributes: { falsy: true } }, + { insert: "c", attributes: { falsy: false } }, + { insert: "d", attributes: { falsy: true } }, + { insert: "e", attributes: { falsy: false } }, + ]); + t.deepEqual(delta, [ + { retain: 2 }, + { retain: 1, attributes: { falsy: false } }, + ]); + compare(users); +}); + +test.skip("testMultilineFormat", (t) => { + const ydoc = new Y.Doc(); + const testText = ydoc.getOrCreateText("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.deepEqual(testText.toDelta(), [ + { insert: "Test", attributes: { bold: true } }, + { insert: "\n" }, + { insert: "Multi-line", attributes: { bold: true } }, + { insert: "\n" }, + { insert: "Formatting", attributes: { bold: true } }, + ]); +}); + +test.skip("testNotMergeEmptyLinesFormat", (t) => { + const ydoc = new Y.Doc(); + const testText = ydoc.getOrCreateText("test"); + testText.applyDelta([ + { insert: "Text" }, + { insert: "\n", attributes: { title: true } }, + { insert: "\nText" }, + { insert: "\n", attributes: { title: true } }, + ]); + t.deepEqual(testText.toDelta(), [ + { insert: "Text" }, + { insert: "\n", attributes: { title: true } }, + { insert: "\nText" }, + { insert: "\n", attributes: { title: true } }, + ]); +}); + +test.skip("testPreserveAttributesThroughDelete", (t) => { + const ydoc = new Y.Doc(); + const testText = ydoc.getOrCreateText("test"); + testText.applyDelta([ + { insert: "Text" }, + { insert: "\n", attributes: { title: true } }, + { insert: "\n" }, + ]); + testText.applyDelta([ + { retain: 4 }, + { delete: 1 }, + { retain: 1, attributes: { title: true } }, + ]); + t.deepEqual(testText.toDelta(), [ + { insert: "Text" }, + { insert: "\n", attributes: { title: true } }, + ]); +}); + +test.skip("testGetDeltaWithEmbeds", (t) => { + const { text0 } = init(gen, { users: 1 }); + assert(text0); + text0.applyDelta([ + { + insert: { linebreak: "s" }, + }, + ]); + t.deepEqual(text0.toDelta(), [ + { + insert: { linebreak: "s" }, + }, + ]); +}); + +test.skip("testTypesAsEmbed", (t) => { + const { text0, text1, testConnector } = init(gen, { users: 2 }); + text0.applyDelta([ + { + insert: new Y.Map([["key", "val"]]), + }, + ]); + t.deepEqual(text0.toDelta()[0].insert.toJSON(), { key: "val" }); + let firedEvent = false; + text1.observe((event) => { + const d = event.delta; + t.assert(d.length === 1); + t.deepEqual( + d.map((x) => /** @type {Y.AbstractType} */ x.insert.toJSON()), + [{ key: "val" }], + ); + firedEvent = true; + }); + testConnector.flushAllMessages(); + const delta = text1.toDelta(); + t.assert(delta.length === 1); + t.deepEqual(delta[0].insert.toJSON(), { key: "val" }); + t.assert(firedEvent, "fired the event observer containing a Type-Embed"); +}); + +test.skip("testSnapshot", (t) => { + const { text0 } = init(gen, { users: 1 }); + assert(text0); + + const doc0 = text0.doc; + doc0.gc = false; + text0.applyDelta([ + { + insert: "abcd", + }, + ]); + const snapshot1 = Y.snapshot(doc0); + text0.applyDelta([ + { + 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.deepEqual(state1, [{ insert: "abcd" }]); + const state2 = text0.toDelta(snapshot2); + t.deepEqual(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.deepEqual(state2Diff, [ + { insert: "a" }, + { insert: "x", attributes: { ychange: { type: "added" } } }, + { insert: "b", attributes: { ychange: { type: "removed" } } }, + { insert: "cd" }, + ]); +}); + +test.skip("testSnapshotDeleteAfter", (t) => { + const { text0 } = init(gen, { users: 1 }); + const doc0 = text0.doc; + doc0.gc = false; + text0.applyDelta([ + { + insert: "abcd", + }, + ]); + const snapshot1 = Y.snapshot(doc0); + text0.applyDelta([ + { + retain: 4, + }, + { + insert: "e", + }, + ]); + const state1 = text0.toDelta(snapshot1); + t.deepEqual(state1, [{ insert: "abcd" }]); +}); + +test.skip("testToJson", (t) => { + const { text0 } = init(gen, { users: 1 }); + assert(text0); + text0.insert(0, "abc", { bold: true }); + t.assert(text0.toJSON() === "abc", "toJSON returns the unformatted text"); +}); + +test.skip("testToDeltaEmbedAttributes", (t) => { + const { text0 } = init(gen, { users: 1 }); + assert(text0); + + text0.insert(0, "ab", { bold: true }); + text0.insertEmbed(1, { image: "imageSrc.png" }, { width: 100 }); + const delta0 = text0.toDelta(); + t.deepEqual(delta0, [ + { insert: "a", attributes: { bold: true } }, + { insert: { image: "imageSrc.png" }, attributes: { width: 100 } }, + { insert: "b", attributes: { bold: true } }, + ]); +}); + +test.skip("testToDeltaEmbedNoAttributes", (t) => { + const { text0 } = init(gen, { users: 1 }); + assert(text0); + + text0.insert(0, "ab", { bold: true }); + text0.insertEmbed(1, { image: "imageSrc.png" }); + const delta0 = text0.toDelta(); + t.deepEqual( + 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", + ); +}); + +test.skip("testFormattingRemoved", (t) => { + const { text0 } = init(gen, { users: 1 }); + assert(text0); + + text0.insert(0, "ab", { bold: true }); + text0.delete(0, 2); + t.assert(Y.getTypeChildren(text0).length === 1); +}); + +test.skip("testFormattingRemovedInMidText", (t) => { + const { text0 } = init(gen, { users: 1 }); + text0.insert(0, "1234"); + text0.insert(2, "ab", { bold: true }); + text0.delete(2, 2); + t.assert(Y.getTypeChildren(text0).length === 3); +}); + +/** + * Reported in https://github.com/yjs/yjs/issues/344 + */ +test.skip("testFormattingDeltaUnnecessaryAttributeChange", (t) => { + const { text0, text1, testConnector } = init(gen, { users: 2 }); + text0.insert(0, "\n", { + PARAGRAPH_STYLES: "normal", + LIST_STYLES: "bullet", + }); + text0.insert(1, "abc", { + PARAGRAPH_STYLES: "normal", + }); + testConnector.flushAllMessages(); + /** + * @type {Array} + */ + const deltas: Array = []; + text0.observe((event) => { + deltas.push(event.delta); + }); + text1.observe((event) => { + deltas.push(event.delta); + }); + text1.format(0, 1, { LIST_STYLES: "number" }); + testConnector.flushAllMessages(); + const filteredDeltas = deltas.filter((d) => d.length > 0); + t.assert(filteredDeltas.length === 2); + t.deepEqual(filteredDeltas[0], [ + { retain: 1, attributes: { LIST_STYLES: "number" } }, + ]); + t.deepEqual(filteredDeltas[0], filteredDeltas[1]); +}); + +test.skip("testInsertAndDeleteAtRandomPositions", (t) => { + const N = 100000; + const { text0 } = init(gen, { users: 1 }); + assert(text0); + + // create initial content + // let expectedResult = init + text0.insert(0, prng.word(gen, N / 2, N / 2)); + + // apply changes + for (let i = 0; i < N; i++) { + const pos = prng.uint32(gen, 0, text0.length); + if (prng.bool(gen)) { + const len = prng.uint32(gen, 1, 5); + const word = prng.word(gen, 0, len); + text0.insert(pos, word); + // expectedResult = expectedResult.slice(0, pos) + word + expectedResult.slice(pos) + } else { + const len = prng.uint32(gen, 0, math.min(3, text0.length - pos)); + text0.delete(pos, len); + // expectedResult = expectedResult.slice(0, pos) + expectedResult.slice(pos + len) + } + } + // deepEqual(text0.toString(), expectedResult) + t.describe("final length", "" + text0.length); +}); + +test.skip("testAppendChars", (t) => { + const N = 10000; + const { text0 } = init(gen, { users: 1 }); + assert(text0); + + // apply changes + for (let i = 0; i < N; i++) { + text0.insert(text0.length, "a"); + } + t.assert(text0.length === N); +}); + +const largeDocumentSize = 100000; + +// const id = Y.createID(0, 0); +// const c = new Y.ContentString("a"); + +test.skip("testBestCase", (t) => { + const N = largeDocumentSize; + const items = new Array(N); + t.measureTime("time to create two million items in the best case", () => { + const parent = /** @type {any} */ {}; + let prevItem = null; + for (let i = 0; i < N; i++) { + /** + * @type {Y.Item} + */ + const n: Y.Item = new Y.Item( + Y.createID(0, 0), + null, + null, + null, + null, + null, + null, + c, + ); + // items.push(n) + items[i] = n; + n.right = prevItem; + n.rightOrigin = prevItem ? id : null; + n.content = c; + n.parent = parent; + prevItem = n; + } + }); + const newArray = new Array(N); + t.measureTime("time to copy two million items to new Array", () => { + for (let i = 0; i < N; i++) { + newArray[i] = items[i]; + } + }); +}); + +const tryGc = () => { + // @ts-ignore + if (typeof global !== "undefined" && global.gc) { + // @ts-ignore + global.gc(); + } +}; + +test.skip("testLargeFragmentedDocument", (t) => { + const itemsToInsert = largeDocumentSize; + let update = /** @type {any} */ null; + (() => { + const doc1 = new Y.Doc(); + const text0 = doc1.getOrCreateText("txt"); + tryGc(); + t.measureTime(`time to insert ${itemsToInsert} items`, () => { + doc1.transact(() => { + for (let i = 0; i < itemsToInsert; i++) { + text0.insert(0, "0"); + } + }); + }); + tryGc(); + t.measureTime("time to encode document", () => { + update = Y.encodeStateAsUpdateV2(doc1); + }); + t.describe("Document size:", update.byteLength); + })(); + (() => { + const doc2 = new Y.Doc(); + tryGc(); + t.measureTime(`time to apply ${itemsToInsert} updates`, () => { + Y.applyUpdateV2(doc2, update); + }); + })(); +}); + +test.skip("testIncrementalUpdatesPerformanceOnLargeFragmentedDocument", (t) => { + const itemsToInsert = largeDocumentSize; + const updates = /** @type {Array} */ []; + (() => { + const doc1 = new Y.Doc(); + doc1.onUpdate((update) => { + updates.push(update); + }); + const text0 = doc1.getText("txt"); + tryGc(); + t.measureTime(`time to insert ${itemsToInsert} items`, () => { + doc1.transact(() => { + for (let i = 0; i < itemsToInsert; i++) { + text0.insert(0, "0"); + } + }); + }); + tryGc(); + })(); + (() => { + t.measureTime( + `time to merge ${itemsToInsert} updates (differential updates)`, + () => { + Y.mergeUpdates(updates); + }, + ); + tryGc(); + t.measureTime( + `time to merge ${itemsToInsert} updates (ydoc updates)`, + () => { + const ydoc = new Y.Doc(); + updates.forEach((update) => { + Y.applyUpdate(ydoc, update); + }); + }, + ); + })(); +}); + +/** + * Splitting surrogates can lead to invalid encoded documents. + * + * https://github.com/yjs/yjs/issues/248 + */ +test.skip("testSplitSurrogateCharacter", (t) => { + { + const { users, text0 } = init(gen, { users: 2 }); + assert(text0); + users[1].disconnect(); // disconnecting forces the user to encode the split surrogate + text0.insert(0, "👾"); // insert surrogate character + // split surrogate, which should not lead to an encoding error + text0.insert(1, "hi!"); + compare(users); + } + { + const { users, text0 } = init(gen, { users: 2 }); + assert(text0); + users[1].disconnect(); // disconnecting forces the user to encode the split surrogate + text0.insert(0, "👾👾"); // insert surrogate character + // partially delete surrogate + text0.delete(1, 2); + compare(users); + } + { + const { users, text0 } = init(gen, { users: 2 }); + assert(text0); + users[1].disconnect(); // disconnecting forces the user to encode the split surrogate + text0.insert(0, "👾👾"); // insert surrogate character + // formatting will also split surrogates + text0.format(1, 2, { bold: true }); + compare(users); + } +}); + +/** + * Search marker bug https://github.com/yjs/yjs/issues/307 + */ +test.skip("testSearchMarkerBug1", (t) => { + const { users, text0, text1, testConnector } = init(gen, { users: 2 }); + assert(text0); + + users[0].onUpdate((update) => { + users[0].transact(() => { + Y.applyUpdate(users[0], update); + }); + }); + users[0].onUpdate((update) => { + users[1].transact(() => { + Y.applyUpdate(users[1], update); + }); + }); + + text0.insert(0, "a_a"); + testConnector.flushAllMessages(); + text0.insert(2, "s"); + testConnector.flushAllMessages(); + text1.insert(3, "d"); + testConnector.flushAllMessages(); + text0.delete(0, 5); + testConnector.flushAllMessages(); + text0.insert(0, "a_a"); + testConnector.flushAllMessages(); + text0.insert(2, "s"); + testConnector.flushAllMessages(); + text1.insert(3, "d"); + testConnector.flushAllMessages(); + deepEqual(text0.toString(), text1.toString()); + deepEqual(text0.toString(), "a_sda"); + compare(users); +}); + +/** + * Reported in https://github.com/yjs/yjs/pull/32 + */ +test.skip("testFormattingBug", (t) => { + const ydoc1 = new Y.Doc(); + const ydoc2 = new Y.Doc(); + const text1 = ydoc1.getText(); + text1.insert(0, "\n\n\n"); + text1.format(0, 3, { url: "http://example.com" }); + ydoc1.getText().format(1, 1, { url: "http://docs.yjs.dev" }); + 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.deepEqual(text1.toDelta(), expectedResult); + t.deepEqual(text1.toDelta(), text2.toDelta()); + console.log(text1.toDelta()); +}); + +/** + * Delete formatting should not leave redundant formatting items. + * + * @param {t.TestCase} _tc + */ +test.skip("testDeleteFormatting", (t) => { + const doc = new Y.Doc(); + const text = doc.getText(); + text.insert(0, "Attack ships on fire off the shoulder of Orion."); + + const doc2 = new Y.Doc(); + const text2 = doc2.getText(); + Y.applyUpdate(doc2, Y.encodeStateAsUpdate(doc)); + + text.format(13, 7, { bold: true }); + Y.applyUpdate(doc2, Y.encodeStateAsUpdate(doc)); + + 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.deepEqual(text.toDelta(), expected); + t.deepEqual(text2.toDelta(), expected); +}); + +// RANDOM TESTS + +let charCounter = 0; + +/** + * Random tests for pure text operations without formatting. + * + * @type Array + */ +const textChanges: Array<(arg0: any, arg1: prng.PRNG) => void> = [ + (y: Y.Doc, gen: prng.PRNG) => { + // insert text + const ytext = y.getOrCreateText("text"); + const insertPos = prng.int32(gen, 0, ytext.length); + const text = charCounter++ + prng.word(gen); + const prevText = ytext.toString(); + ytext.insert(insertPos, text); + deepEqual( + ytext.toString(), + prevText.slice(0, insertPos) + text + prevText.slice(insertPos), + ); + }, + /** + * @param {Y.Doc} y + * @param {prng.PRNG} gen + */ + (y: Y.Doc, gen: prng.PRNG) => { + // delete text + const ytext = y.getOrCreateText("text"); + const contentLen = ytext.toString().length; + const insertPos = prng.int32(gen, 0, contentLen); + const overwrite = math.min(prng.int32(gen, 0, contentLen - insertPos), 2); + const prevText = ytext.toString(); + ytext.delete(insertPos, overwrite); + deepEqual( + ytext.toString(), + prevText.slice(0, insertPos) + prevText.slice(insertPos + overwrite), + ); + }, +]; + +test.skip("testRepeatGenerateTextChanges5", (t) => { + const { users } = checkResult(applyRandomTests(gen, textChanges, 5)); + const cleanups = Y.cleanupYTextFormatting(users[0].getText("text")); + t.assert(cleanups === 0); +}); + +test.skip("testRepeatGenerateTextChanges30", (t) => { + const { users } = checkResult(applyRandomTests(gen, textChanges, 30)); + const cleanups = Y.cleanupYTextFormatting(users[0].getText("text")); + t.assert(cleanups === 0); +}); + +test.skip("testRepeatGenerateTextChanges40", (t) => { + const { users } = checkResult(applyRandomTests(gen, textChanges, 40)); + const cleanups = Y.cleanupYTextFormatting(users[0].getText("text")); + t.assert(cleanups === 0); +}); + +test.skip("testRepeatGenerateTextChanges50", (t) => { + const { users } = checkResult(applyRandomTests(gen, textChanges, 50)); + const cleanups = Y.cleanupYTextFormatting(users[0].getText("text")); + t.assert(cleanups === 0); +}); + +test.skip("testRepeatGenerateTextChanges70", (t) => { + const { users } = checkResult(applyRandomTests(gen, textChanges, 70)); + const cleanups = Y.cleanupYTextFormatting(users[0].getText("text")); + t.assert(cleanups === 0); +}); + +test.skip("testRepeatGenerateTextChanges90", (t) => { + const { users } = checkResult(applyRandomTests(gen, textChanges, 90)); + const cleanups = Y.cleanupYTextFormatting(users[0].getText("text")); + t.assert(cleanups === 0); +}); + +test.skip("testRepeatGenerateTextChanges300", (t) => { + const { users } = checkResult(applyRandomTests(gen, textChanges, 300)); + const cleanups = Y.cleanupYTextFormatting(users[0].getText("text")); + t.assert(cleanups === 0); +}); + +const marks = [ + { bold: true }, + { italic: true }, + { italic: true, color: "#888" }, +]; + +const marksChoices = [undefined, ...marks]; + +/** + * Random tests for all features of y-text (formatting, embeds, ..). + * + * @type Array + */ +const qChanges: Array<(arg0: any, arg1: prng.PRNG) => void> = [ + /** + * @param {Y.Doc} y + * @param {prng.PRNG} gen + */ + (y: Y.Doc, gen: prng.PRNG) => { + // insert text + const ytext = y.getOrCreateText("text"); + const insertPos = prng.int32(gen, 0, ytext.length); + const attrs = prng.oneOf(gen, marksChoices); + const text = charCounter++ + prng.word(gen); + ytext.insert(insertPos, text, attrs); + }, + /** + * @param {Y.Doc} y + * @param {prng.PRNG} gen + */ + (y: Y.Doc, gen: prng.PRNG) => { + // insert embed + const ytext = y.getOrCreateText("text"); + const insertPos = prng.int32(gen, 0, ytext.length); + if (prng.bool(gen)) { + ytext.insertEmbed(insertPos, { + image: + "https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png", + }); + } else { + ytext.insertEmbed( + insertPos, + new Y.Map([[prng.word(gen), prng.word(gen)]]), + ); + } + }, + /** + * @param {Y.Doc} y + * @param {prng.PRNG} gen + */ + (y: Y.Doc, gen: prng.PRNG) => { + // delete text + const ytext = y.getOrCreateText("text"); + const contentLen = ytext.toString().length; + const insertPos = prng.int32(gen, 0, contentLen); + const overwrite = math.min(prng.int32(gen, 0, contentLen - insertPos), 2); + ytext.delete(insertPos, overwrite); + }, + /** + * @param {Y.Doc} y + * @param {prng.PRNG} gen + */ + (y: Y.Doc, gen: prng.PRNG) => { + // format text + const ytext = y.getOrCreateText("text"); + const contentLen = ytext.toString().length; + const insertPos = prng.int32(gen, 0, contentLen); + const overwrite = math.min(prng.int32(gen, 0, contentLen - insertPos), 2); + const format = prng.oneOf(gen, marks); + ytext.format(insertPos, overwrite, format); + }, + /** + * @param {Y.Doc} y + * @param {prng.PRNG} gen + */ + (y: Y.Doc, gen: prng.PRNG) => { + // insert codeblock + const ytext = y.getOrCreateText("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 }); + } + ops.push( + { insert: text }, + { insert: "\n", format: { "code-block": true } }, + ); + ytext.applyDelta(ops); + }, + /** + * @param {Y.Doc} y + * @param {prng.PRNG} gen + */ + (y: Y.Doc, gen: prng.PRNG) => { + // complex delta op + const ytext = y.getOrCreateText("text"); + const contentLen = ytext.toString().length; + let currentPos = math.max(0, prng.int32(gen, 0, contentLen - 1)); + /** + * @type {Array} + */ + const ops: Array = currentPos > 0 ? [{ 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); + ops.push({ retain, attributes: format }); + currentPos += retain; + }, + () => { + // insert + const attrs = prng.oneOf(gen, marksChoices); + const text = prng.word(gen, 1, 3); + ops.push({ insert: text, attributes: attrs }); + }, + () => { + // delete + const delLen = math.min( + prng.int32(gen, 0, contentLen - currentPos), + 10, + ); + ops.push({ delete: delLen }); + currentPos += delLen; + }, + ])(); + } + ytext.applyDelta(ops); + }, +]; + +const checkResult = (result: any) => { + for (let i = 1; i < result.testObjects.length; i++) { + /** + * @param {any} d + */ + const typeToObject = (d: any) => + Y.isAbstractType(d.insert) ? 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.deepEqual(p1, p2); + } + // Uncomment this to find formatting-cleanup issues + // const cleanups = Y.cleanupYTextFormatting(result.users[0].getText('text')) + // t.assert(cleanups === 0) + return result; +}; + +test.skip("testRepeatGenerateQuillChanges1", (t) => { + const { users } = checkResult(applyRandomTests(gen, qChanges, 1)); + const cleanups = Y.cleanupYTextFormatting(users[0].getText("text")); + t.assert(cleanups === 0); +}); + +test.skip("testRepeatGenerateQuillChanges2", (t) => { + const { users } = checkResult(applyRandomTests(gen, qChanges, 2)); + const cleanups = Y.cleanupYTextFormatting(users[0].getText("text")); + t.assert(cleanups === 0); +}); + +test.skip("testRepeatGenerateQuillChanges2Repeat", (t) => { + for (let i = 0; i < 1000; i++) { + const { users } = checkResult(applyRandomTests(gen, qChanges, 2)); + const cleanups = Y.cleanupYTextFormatting(users[0].getText("text")); + t.assert(cleanups === 0); + } +}); + +test.skip("testRepeatGenerateQuillChanges3", (t) => { + checkResult(applyRandomTests(gen, qChanges, 3)); +}); + +test.skip("testRepeatGenerateQuillChanges30", (t) => { + checkResult(applyRandomTests(gen, qChanges, 30)); +}); + +test.skip("testRepeatGenerateQuillChanges40", (t) => { + checkResult(applyRandomTests(gen, qChanges, 40)); +}); + +test.skip("testRepeatGenerateQuillChanges70", (t) => { + checkResult(applyRandomTests(gen, qChanges, 70)); +}); + +test.skip("testRepeatGenerateQuillChanges100", (t) => { + checkResult(applyRandomTests(gen, qChanges, 100)); +}); + +test.skip("testRepeatGenerateQuillChanges300", (t) => { + checkResult(applyRandomTests(gen, qChanges, 300)); +}); From ff506792e4218d2b28a765a94205a71c340fd8eb Mon Sep 17 00:00:00 2001 From: DarkSky Date: Mon, 8 Jul 2024 14:50:51 +0800 Subject: [PATCH 18/75] feat: add some compatibility for yjs's api --- y-octo-node/index.d.ts | 12 ++++-- y-octo-node/src/map.rs | 78 ++++++++++++++++++++++++++++------ y-octo-node/src/text.rs | 27 +++++++++++- y-octo-node/tests/map.spec.ts | 2 +- y-octo-node/tests/text.spec.ts | 6 +-- 5 files changed, 104 insertions(+), 21 deletions(-) diff --git a/y-octo-node/index.d.ts b/y-octo-node/index.d.ts index ef8f2f4..d4e12e1 100644 --- a/y-octo-node/index.d.ts +++ b/y-octo-node/index.d.ts @@ -54,10 +54,12 @@ export class Doc { } export class YMap { get length(): number + get size(): number get isEmpty(): boolean get(key: string): T - set(key: string, value: YArray | YMap | YText | boolean | number | string | Record | null | undefined): void - remove(key: string): void + set | null | undefined>(key: string, value: T): T + delete(key: string): void + clear(): void toJson(): object entries(): YMapEntriesIterator keys(): YMapKeyIterator @@ -75,9 +77,13 @@ export class YText { get len(): number get isEmpty(): boolean insert(index: number, str: string): void - remove(index: number, len: number): void + delete(index: number, len: number): void get length(): number + applyDelta(delta: JsArray): void + toDelta(): JsArray toString(): string + observe(callback: (...args: any[]) => any): void + observeDeep(callback: (...args: any[]) => any): void } export type YStore = Store export class Store { } diff --git a/y-octo-node/src/map.rs b/y-octo-node/src/map.rs index 9314603..327a6be 100644 --- a/y-octo-node/src/map.rs +++ b/y-octo-node/src/map.rs @@ -19,6 +19,11 @@ impl YMap { self.map.len() as i64 } + #[napi(getter)] + pub fn size(&self) -> i64 { + self.length() + } + #[napi(getter)] pub fn is_empty(&self) -> bool { self.map.is_empty() @@ -41,25 +46,52 @@ impl YMap { } #[napi( - ts_args_type = "key: string, value: YArray | YMap | YText | boolean | number | string | Record | \ - null | undefined" + ts_generic_types = "T = YArray | YMap | YText | boolean | number | string | Record | null | undefined", + ts_args_type = "key: string, value: T", + ts_return_type = "T" )] - pub fn set(&mut self, key: String, value: MixedRefYType) -> Result<()> { + pub fn set(&mut self, env: Env, key: String, value: MixedRefYType) -> Result { match value { - MixedRefYType::A(array) => self.map.insert(key, array.array.clone()).map_err(anyhow::Error::from), - MixedRefYType::B(map) => self.map.insert(key, map.map.clone()).map_err(anyhow::Error::from), - MixedRefYType::C(text) => self.map.insert(key, text.text.clone()).map_err(anyhow::Error::from), + MixedRefYType::A(array) => { + self.map.insert(key, array.array.clone()).map_err(anyhow::Error::from)?; + Ok(MixedYType::A(YArray::inner_new(array.array.clone()))) + } + MixedRefYType::B(map) => { + self.map.insert(key, map.map.clone()).map_err(anyhow::Error::from)?; + Ok(MixedYType::B(YMap::inner_new(map.map.clone()))) + } + MixedRefYType::C(text) => { + self.map.insert(key, text.text.clone()).map_err(anyhow::Error::from)?; + Ok(MixedYType::C(YText::inner_new(text.text.clone()))) + } MixedRefYType::D(unknown) => match unknown.get_type() { Ok(value_type) => match value_type { ValueType::Undefined | ValueType::Null => { - self.map.insert(key, Any::Null).map_err(anyhow::Error::from) + self.map.insert(key, Any::Null).map_err(anyhow::Error::from)?; + Ok(MixedYType::D( + env.get_null().map(|v| v.into_unknown()).map_err(anyhow::Error::from)?, + )) } ValueType::Boolean => match unknown.coerce_to_bool().and_then(|v| v.get_value()) { - Ok(boolean) => self.map.insert(key, boolean).map_err(anyhow::Error::from), + Ok(boolean) => { + self.map.insert(key, boolean).map_err(anyhow::Error::from)?; + Ok(MixedYType::D( + env.get_boolean(boolean) + .map(|v| v.into_unknown()) + .map_err(anyhow::Error::from)?, + )) + } Err(e) => Err(anyhow::Error::from(e).context("Failed to coerce value to boolean")), }, ValueType::Number => match unknown.coerce_to_number().and_then(|v| v.get_double()) { - Ok(number) => self.map.insert(key, number).map_err(anyhow::Error::from), + Ok(number) => { + self.map.insert(key, number).map_err(anyhow::Error::from)?; + Ok(MixedYType::D( + env.create_double(number) + .map(|v| v.into_unknown()) + .map_err(anyhow::Error::from)?, + )) + } Err(e) => Err(anyhow::Error::from(e).context("Failed to coerce value to number")), }, ValueType::String => { @@ -68,12 +100,26 @@ impl YMap { .and_then(|v| v.into_utf8()) .and_then(|s| s.as_str().map(|s| s.to_string())) { - Ok(string) => self.map.insert(key, string).map_err(anyhow::Error::from), + Ok(string) => { + self.map.insert(key, string.clone()).map_err(anyhow::Error::from)?; + Ok(MixedYType::D( + env.create_string(&string) + .map(|v| v.into_unknown()) + .map_err(anyhow::Error::from)?, + )) + } Err(e) => Err(anyhow::Error::from(e).context("Failed to coerce value to string")), } } ValueType::Object => match unknown.coerce_to_object().and_then(get_any_from_js_object) { - Ok(any) => self.map.insert(key, Value::Any(any)).map_err(anyhow::Error::from), + Ok(any) => { + self.map + .insert(key, Value::Any(any.clone())) + .map_err(anyhow::Error::from)?; + Ok(MixedYType::D( + get_js_unknown_from_any(env, any).map_err(anyhow::Error::from)?.into(), + )) + } Err(e) => Err(anyhow::Error::from(e).context("Failed to coerce value to object")), }, ValueType::Symbol => Err(anyhow::Error::msg("Symbol values are not supported")), @@ -87,10 +133,18 @@ impl YMap { } #[napi] - pub fn remove(&mut self, key: String) { + pub fn delete(&mut self, key: String) { self.map.remove(&key); } + #[napi] + pub fn clear(&mut self) { + let keys = self.map.keys().map(ToOwned::to_owned).collect::>(); + for key in keys { + self.map.remove(&key); + } + } + #[napi] pub fn to_json(&self, env: Env) -> Result { let mut js_object = env.create_object()?; diff --git a/y-octo-node/src/text.rs b/y-octo-node/src/text.rs index ca67c49..fa60744 100644 --- a/y-octo-node/src/text.rs +++ b/y-octo-node/src/text.rs @@ -1,3 +1,4 @@ +use napi::{bindgen_prelude::Array as JsArray, Env, JsFunction}; use y_octo::Text; use super::*; @@ -35,7 +36,7 @@ impl YText { } #[napi] - pub fn remove(&mut self, index: i64, len: i64) -> Result<()> { + pub fn delete(&mut self, index: i64, len: i64) -> Result<()> { self.text.remove(index as u64, len as u64).map_err(anyhow::Error::from) } @@ -44,11 +45,33 @@ impl YText { self.text.len() as i64 } + #[napi] + pub fn apply_delta(&mut self, env: Env, _delta: JsArray) -> Result<()> { + unimplemented!() + } + + #[napi] + pub fn to_delta(&self, env: Env) -> Result { + unimplemented!() + } + #[allow(clippy::inherent_to_string)] #[napi] pub fn to_string(&self) -> String { self.text.to_string() } + + // TODO(@darkskygit): impl type based observe + #[napi] + pub fn observe(&mut self, _callback: JsFunction) -> Result<()> { + Ok(()) + } + + // TODO(@darkskygit): impl type based observe + #[napi] + pub fn observe_deep(&mut self, _callback: JsFunction) -> Result<()> { + Ok(()) + } } #[cfg(test)] @@ -70,7 +93,7 @@ mod tests { assert_eq!(text.to_string(), "hello"); text.insert(5, " world".into()).unwrap(); assert_eq!(text.to_string(), "hello world"); - text.remove(5, 6).unwrap(); + text.delete(5, 6).unwrap(); assert_eq!(text.to_string(), "hello"); } } diff --git a/y-octo-node/tests/map.spec.ts b/y-octo-node/tests/map.spec.ts index b355b22..eb33490 100644 --- a/y-octo-node/tests/map.spec.ts +++ b/y-octo-node/tests/map.spec.ts @@ -34,7 +34,7 @@ test("map editing", (t) => { t.is(map.get("c"), 1); t.is(map.get("d"), "hello world"); t.is(map.length, 4); - map.remove("b"); + map.delete("b"); t.is(map.length, 3); t.is(map.get("d"), "hello world"); }); diff --git a/y-octo-node/tests/text.spec.ts b/y-octo-node/tests/text.spec.ts index e0f8bab..e38c73e 100644 --- a/y-octo-node/tests/text.spec.ts +++ b/y-octo-node/tests/text.spec.ts @@ -27,11 +27,11 @@ test("text editing", (t) => { text.insert(1, "b"); text.insert(2, "c"); t.is(text.toString(), "abc"); - text.remove(0, 1); + text.delete(0, 1); t.is(text.toString(), "bc"); - text.remove(1, 1); + text.delete(1, 1); t.is(text.toString(), "b"); - text.remove(0, 1); + text.delete(0, 1); t.is(text.toString(), ""); }); From 85fc8aed9971d0f59077c2ed074aac48df9e6ba2 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Mon, 8 Jul 2024 15:14:29 +0800 Subject: [PATCH 19/75] feat: compare ids & test typing fix --- y-octo-node/index.d.ts | 4 + y-octo-node/index.js | 4 +- y-octo-node/src/function.rs | 9 +++ y-octo-node/src/types.rs | 7 +- y-octo-node/tests/yjs/testHelper.ts | 23 +++--- y-octo-node/tests/yjs/y-array.spec.ts | 47 +++++------- y-octo-node/tests/yjs/y-map.spec.ts | 102 +++++++++----------------- y-octo-node/tests/yjs/y-text.spec.ts | 27 ------- y-octo/src/doc/types/map.rs | 9 +++ 9 files changed, 96 insertions(+), 136 deletions(-) diff --git a/y-octo-node/index.d.ts b/y-octo-node/index.d.ts index d4e12e1..c92064e 100644 --- a/y-octo-node/index.d.ts +++ b/y-octo-node/index.d.ts @@ -6,6 +6,7 @@ export declare function encodeStateAsUpdate(doc: Doc, state?: Buffer | undefined | null): Buffer export declare function encodeStateVector(doc: Doc): Buffer export declare function compareStructStores(store: YStore, other: YStore): boolean +export declare function compareIds(a?: YId | undefined | null, b?: YId | undefined | null): boolean export declare function createDeleteSetFromStructStore(store: YStore): YDeleteSet export declare function equalDeleteSets(a: YDeleteSet, b: YDeleteSet): boolean export declare function snapshot(doc: Doc): YSnapshot @@ -56,6 +57,7 @@ export class YMap { get length(): number get size(): number get isEmpty(): boolean + get itemId(): YId | null get(key: string): T set | null | undefined>(key: string, value: T): T delete(key: string): void @@ -85,6 +87,8 @@ export class YText { observe(callback: (...args: any[]) => any): void observeDeep(callback: (...args: any[]) => any): void } +export type YId = Id +export class Id { } export type YStore = Store export class Store { } export type YDeleteSet = DeleteSet diff --git a/y-octo-node/index.js b/y-octo-node/index.js index 00076bc..81cc47d 100644 --- a/y-octo-node/index.js +++ b/y-octo-node/index.js @@ -252,7 +252,7 @@ if (!nativeBinding) { throw new Error(`Failed to load native binding`) } -const { YArray, YArrayIterator, Doc, encodeStateAsUpdate, encodeStateVector, compareStructStores, createDeleteSetFromStructStore, equalDeleteSets, snapshot, encodeSnapshot, applyUpdate, mergeUpdates, isAbstractType, YMap, YMapEntriesIterator, YMapKeyIterator, YText, Store, DeleteSet, YSnapshot } = nativeBinding +const { YArray, YArrayIterator, Doc, encodeStateAsUpdate, encodeStateVector, compareStructStores, compareIds, createDeleteSetFromStructStore, equalDeleteSets, snapshot, encodeSnapshot, applyUpdate, mergeUpdates, isAbstractType, YMap, YMapEntriesIterator, YMapKeyIterator, YText, Id, Store, DeleteSet, YSnapshot } = nativeBinding module.exports.YArray = YArray module.exports.YArrayIterator = YArrayIterator @@ -260,6 +260,7 @@ module.exports.Doc = Doc module.exports.encodeStateAsUpdate = encodeStateAsUpdate module.exports.encodeStateVector = encodeStateVector module.exports.compareStructStores = compareStructStores +module.exports.compareIds = compareIds module.exports.createDeleteSetFromStructStore = createDeleteSetFromStructStore module.exports.equalDeleteSets = equalDeleteSets module.exports.snapshot = snapshot @@ -271,6 +272,7 @@ module.exports.YMap = YMap module.exports.YMapEntriesIterator = YMapEntriesIterator module.exports.YMapKeyIterator = YMapKeyIterator module.exports.YText = YText +module.exports.Id = Id module.exports.Store = Store module.exports.DeleteSet = DeleteSet module.exports.YSnapshot = YSnapshot diff --git a/y-octo-node/src/function.rs b/y-octo-node/src/function.rs index 2ca35ce..66a67ce 100644 --- a/y-octo-node/src/function.rs +++ b/y-octo-node/src/function.rs @@ -23,6 +23,15 @@ pub fn compare_struct_stores(store: &YStore, other: &YStore) -> bool { store.doc.store_compare(&other.doc) } +#[napi] +pub fn compare_ids(a: Option<&YId>, b: Option<&YId>) -> bool { + match (a, b) { + (Some(a), Some(b)) => a.id == b.id, + (None, None) => true, + _ => false, + } +} + // delete set #[napi] diff --git a/y-octo-node/src/types.rs b/y-octo-node/src/types.rs index 6d112a9..2abb710 100644 --- a/y-octo-node/src/types.rs +++ b/y-octo-node/src/types.rs @@ -1,7 +1,12 @@ -use y_octo::{CrdtWrite, DeleteSet, Doc, RawEncoder, StateVector}; +use y_octo::{CrdtWrite, DeleteSet, Doc, Id, RawEncoder, StateVector}; use super::*; +#[napi(js_name = "Id")] +pub struct YId { + pub(crate) id: Id, +} + #[napi(js_name = "Store")] pub struct YStore { pub(crate) doc: Doc, diff --git a/y-octo-node/tests/yjs/testHelper.ts b/y-octo-node/tests/yjs/testHelper.ts index 70c190d..778a9e7 100644 --- a/y-octo-node/tests/yjs/testHelper.ts +++ b/y-octo-node/tests/yjs/testHelper.ts @@ -247,17 +247,17 @@ export class TestConnector { type InitResult = { testConnector: TestConnector; users: Array; - testObjects?: Array; - array0?: Y.Array; - array1?: Y.Array; - array2?: Y.Array; - map0?: Y.Map; - map1?: Y.Map; - map2?: Y.Map; - map3?: Y.Map; - text0?: Y.Text; - text1?: Y.Text; - text2?: Y.Text; + testObjects: Array; + array0: Y.Array; + array1: Y.Array; + array2: Y.Array; + map0: Y.Map; + map1: Y.Map; + map2: Y.Map; + map3: Y.Map; + text0: Y.Text; + text1: Y.Text; + text2: Y.Text; // xml0: Y.XmlElement; // xml1: Y.XmlElement; // xml2: Y.XmlElement; @@ -274,6 +274,7 @@ export const init = ( useV1Encoding(); } + // @ts-expect-error expect const result: InitResult = { users: [], testConnector: new TestConnector(gen), diff --git a/y-octo-node/tests/yjs/y-array.spec.ts b/y-octo-node/tests/yjs/y-array.spec.ts index f9b90fd..b91ad7b 100644 --- a/y-octo-node/tests/yjs/y-array.spec.ts +++ b/y-octo-node/tests/yjs/y-array.spec.ts @@ -114,7 +114,7 @@ test.skip("testLengthIssue2", (t) => { test.skip("testDeleteInsert", (t) => { const { users, array0 } = init(gen, { users: 2 }); - assert(array0); + array0.delete(0, 0); t.notThrows(() => { array0.delete(1, 1); @@ -129,8 +129,7 @@ test.skip("testDeleteInsert", (t) => { // TODO: impl sync protocol encode in rust test.skip("testInsertThreeElementsTryRegetProperty", (t) => { const { testConnector, users, array0, array1 } = init(gen, { users: 2 }); - assert(array0); - assert(array1); + array0.insert(0, [1, true, false]); t.deepEqual(array0.toJSON(), [1, true, false], ".toJSON() works"); testConnector.flushAllMessages(); @@ -140,9 +139,7 @@ test.skip("testInsertThreeElementsTryRegetProperty", (t) => { test.skip("testConcurrentInsertWithThreeConflicts", (t) => { const { users, array0, array1, array2 } = init(gen, { users: 3 }); - assert(array0); - assert(array1); - assert(array2); + array0.insert(0, [0]); array1.insert(0, [1]); array2.insert(0, [2]); @@ -153,9 +150,7 @@ test.skip("testConcurrentInsertDeleteWithThreeConflicts", (t) => { const { testConnector, users, array0, array1, array2 } = init(gen, { users: 3, }); - assert(array0); - assert(array1); - assert(array2); + array0.insert(0, ["x", "y", "z"]); testConnector.flushAllMessages(); array0.insert(1, [0]); @@ -169,9 +164,7 @@ test.skip("testInsertionsInLateSync", (t) => { const { testConnector, users, array0, array1, array2 } = init(gen, { users: 3, }); - assert(array0); - assert(array1); - assert(array2); + array0.insert(0, ["x", "y"]); testConnector.flushAllMessages(); users[1].disconnect(); @@ -187,8 +180,7 @@ test.skip("testInsertionsInLateSync", (t) => { test.skip("testDisconnectReallyPreventsSendingMessages", (t) => { const { testConnector, users, array0, array1 } = init(gen, { users: 3 }); - assert(array0); - assert(array1); + array0.insert(0, ["x", "y"]); testConnector.flushAllMessages(); users[1].disconnect(); @@ -204,8 +196,7 @@ test.skip("testDisconnectReallyPreventsSendingMessages", (t) => { test.skip("testDeletionsInLateSync", (t) => { const { testConnector, users, array0, array1 } = init(gen, { users: 2 }); - assert(array0); - assert(array1); + array0.insert(0, ["x", "y"]); testConnector.flushAllMessages(); users[1].disconnect(); @@ -217,8 +208,7 @@ test.skip("testDeletionsInLateSync", (t) => { test.skip("testInsertThenMergeDeleteOnSync", (t) => { const { testConnector, users, array0, array1 } = init(gen, { users: 2 }); - assert(array0); - assert(array1); + array0.insert(0, ["x", "y", "z"]); testConnector.flushAllMessages(); users[0].disconnect(); @@ -229,7 +219,7 @@ test.skip("testInsertThenMergeDeleteOnSync", (t) => { test.skip("testInsertAndDeleteEvents", (t) => { const { array0, users } = init(gen, { users: 2 }); - assert(array0); + let event: Record | null = null; array0.observe((e) => { event = e; @@ -248,7 +238,7 @@ test.skip("testInsertAndDeleteEvents", (t) => { test.skip("testNestedObserverEvents", (t) => { const { array0, users } = init(gen, { users: 2 }); - assert(array0); + const vals: number[] = []; array0.observe((e) => { if (array0.length === 1) { @@ -269,7 +259,7 @@ test.skip("testNestedObserverEvents", (t) => { test.skip("testInsertAndDeleteEventsForTypes", (t) => { const { array0, users } = init(gen, { users: 2 }); - assert(array0); + let event: Record | null = null; array0.observe((e) => { event = e; @@ -292,7 +282,7 @@ test.skip("testInsertAndDeleteEventsForTypes", (t) => { */ test.skip("testObserveDeepEventOrder", (t) => { const { array0, users } = init(gen, { users: 2 }); - assert(array0); + let events: any[] = []; array0.observeDeep((e) => { events = e; @@ -332,7 +322,7 @@ test.skip("testObservedeepIndexes", (t) => { test.skip("testChangeEvent", (t) => { const { array0, users } = init(gen, { users: 2 }); - assert(array0); + let changes: any = null; array0.observe((e) => { changes = e.changes; @@ -360,7 +350,7 @@ test.skip("testChangeEvent", (t) => { test.skip("testInsertAndDeleteEventsForTypes2", (t) => { const { array0, users } = init(gen, { users: 2 }); - assert(array0); + const events: Record[] = []; array0.observe((e) => { events.push(e); @@ -381,7 +371,7 @@ test.skip("testInsertAndDeleteEventsForTypes2", (t) => { */ test.skip("testNewChildDoesNotEmitEventInTransaction", (t) => { const { array0, users } = init(gen, { users: 2 }); - assert(array0); + let fired = false; users[0].transact(() => { const newMap = new Y.Map(); @@ -396,7 +386,7 @@ test.skip("testNewChildDoesNotEmitEventInTransaction", (t) => { test.skip("testGarbageCollector", (t) => { const { testConnector, users, array0 } = init(gen, { users: 3 }); - assert(array0); + array0.insert(0, ["x", "y", "z"]); testConnector.flushAllMessages(); users[0].disconnect(); @@ -408,7 +398,7 @@ test.skip("testGarbageCollector", (t) => { test.skip("testEventTargetIsSetCorrectlyOnLocal", (t) => { const { array0, users } = init(gen, { users: 3 }); - assert(array0); + let event: any; array0.observe((e) => { event = e; @@ -420,8 +410,7 @@ test.skip("testEventTargetIsSetCorrectlyOnLocal", (t) => { test.skip("testEventTargetIsSetCorrectlyOnRemote", (t) => { const { testConnector, array0, array1, users } = init(gen, { users: 3 }); - assert(array0); - assert(array1); + let event: any; array0.observe((e) => { event = e; diff --git a/y-octo-node/tests/yjs/y-map.spec.ts b/y-octo-node/tests/yjs/y-map.spec.ts index 7710fd5..bea69f6 100644 --- a/y-octo-node/tests/yjs/y-map.spec.ts +++ b/y-octo-node/tests/yjs/y-map.spec.ts @@ -46,14 +46,14 @@ test.skip("testMapEventError", (t) => { }); test.skip("testMapHavingIterableAsConstructorParamTests", (t) => { - const { map0 } = init(gen, { users: 1 }); + const { users, map0 } = init(gen, { users: 1 }); - const m1 = new Y.Map(Object.entries({ number: 1, string: "hello" })); + const m1 = users[0].createMap(Object.entries({ number: 1, string: "hello" })); map0.set("m1", m1); t.assert(m1.get("number") === 1); t.assert(m1.get("string") === "hello"); - const m2 = new Y.Map([ + const m2 = users[0].createMap([ ["object", { x: 1 }], ["boolean", true], ]); @@ -61,7 +61,7 @@ test.skip("testMapHavingIterableAsConstructorParamTests", (t) => { t.assert(m2.get("object").x === 1); t.assert(m2.get("boolean") === true); - const m3 = new Y.Map([...m1, ...m2]); + const m3 = users[0].createMap([...m1, ...m2]); map0.set("m3", m3); t.assert(m3.get("number") === 1); t.assert(m3.get("string") === "hello"); @@ -71,9 +71,6 @@ test.skip("testMapHavingIterableAsConstructorParamTests", (t) => { test.skip("testBasicMapTests", (t) => { const { testConnector, users, map0, map1, map2 } = init(gen, { users: 3 }); - assert(map0); - assert(map1); - assert(map2); users[2].disconnect(); @@ -178,7 +175,6 @@ test.skip("testBasicMapTests", (t) => { test.skip("testGetAndSetOfMapProperty", (t) => { const { testConnector, users, map0 } = init(gen, { users: 2 }); - assert(map0); map0.set("stuff", "stuffy"); map0.set("undefined", undefined); @@ -198,7 +194,6 @@ test.skip("testGetAndSetOfMapProperty", (t) => { test.skip("testYmapSetsYmap", (t) => { const { users, map0 } = init(gen, { users: 2 }); - assert(map0); const map = map0.set("Map", new Y.Map()); t.assert(map0.get("Map") === map); @@ -209,7 +204,6 @@ test.skip("testYmapSetsYmap", (t) => { test.skip("testYmapSetsYarray", (t) => { const { users, map0 } = init(gen, { users: 2 }); - assert(map0); const array = map0.set("Array", new Y.Array()); t.assert(array === map0.get("Array")); @@ -221,7 +215,6 @@ test.skip("testYmapSetsYarray", (t) => { test.skip("testGetAndSetOfMapPropertySyncs", (t) => { const { testConnector, users, map0 } = init(gen, { users: 2 }); - assert(map0); map0.set("stuff", "stuffy"); t.deepEqual(map0.get("stuff"), "stuffy"); @@ -235,8 +228,6 @@ test.skip("testGetAndSetOfMapPropertySyncs", (t) => { test.skip("testGetAndSetOfMapPropertyWithConflict", (t) => { const { testConnector, users, map0, map1 } = init(gen, { users: 3 }); - assert(map0); - assert(map1); map0.set("stuff", "c0"); map1.set("stuff", "c1"); @@ -250,7 +241,6 @@ test.skip("testGetAndSetOfMapPropertyWithConflict", (t) => { test.skip("testSizeAndDeleteOfMapProperty", (t) => { const { map0 } = init(gen, { users: 1 }); - assert(map0); map0.set("stuff", "c0"); map0.set("otherstuff", "c1"); @@ -269,8 +259,6 @@ test.skip("testSizeAndDeleteOfMapProperty", (t) => { test.skip("testGetAndSetAndDeleteOfMapProperty", (t) => { const { testConnector, users, map0, map1 } = init(gen, { users: 3 }); - assert(map0); - assert(map1); map0.set("stuff", "c0"); map1.set("stuff", "c1"); @@ -285,7 +273,6 @@ test.skip("testGetAndSetAndDeleteOfMapProperty", (t) => { test.skip("testSetAndClearOfMapProperties", (t) => { const { testConnector, users, map0 } = init(gen, { users: 1 }); - assert(map0); map0.set("stuff", "c0"); map0.set("otherstuff", "c1"); @@ -304,10 +291,6 @@ test.skip("testSetAndClearOfMapPropertiesWithConflicts", (t) => { const { testConnector, users, map0, map1, map2, map3 } = init(gen, { users: 4, }); - assert(map0); - assert(map1); - assert(map2); - assert(map3); map0.set("stuff", "c0"); map1.set("stuff", "c1"); @@ -331,9 +314,6 @@ test.skip("testSetAndClearOfMapPropertiesWithConflicts", (t) => { test.skip("testGetAndSetOfMapPropertyWithThreeConflicts", (t) => { const { testConnector, users, map0, map1, map2 } = init(gen, { users: 3 }); - assert(map0); - assert(map1); - assert(map2); map0.set("stuff", "c0"); map1.set("stuff", "c1"); @@ -351,10 +331,6 @@ test.skip("testGetAndSetAndDeleteOfMapPropertyWithThreeConflicts", (t) => { const { testConnector, users, map0, map1, map2, map3 } = init(gen, { users: 4, }); - assert(map0); - assert(map1); - assert(map2); - assert(map3); map0.set("stuff", "c0"); map1.set("stuff", "c1"); @@ -376,11 +352,8 @@ test.skip("testGetAndSetAndDeleteOfMapPropertyWithThreeConflicts", (t) => { test.skip("testObserveDeepProperties", (t) => { const { testConnector, users, map1, map2, map3 } = init(gen, { users: 4 }); - assert(map1); - assert(map2); - assert(map3); - const _map1 = map1.set("map", new Y.Map()); + const _map1 = map1.set("map", users[0].createMap()); let calls = 0; let dmapid; map1.observeDeep((events) => { @@ -395,18 +368,18 @@ test.skip("testObserveDeepProperties", (t) => { }); }); testConnector.flushAllMessages(); - const _map3 = map3.get("map"); + const _map3 = map3.get("map"); _map3.set("deepmap", new Y.Map()); testConnector.flushAllMessages(); - const _map2 = map2.get("map"); + const _map2 = map2.get("map"); _map2.set("deepmap", new Y.Map()); testConnector.flushAllMessages(); - const dmap1 = _map1.get("deepmap"); - const dmap2 = _map2.get("deepmap"); - const dmap3 = _map3.get("deepmap"); + const dmap1 = _map1.get("deepmap"); + const dmap2 = _map2.get("deepmap"); + const dmap3 = _map3.get("deepmap"); t.assert(calls > 0); - t.assert(compareIDs(dmap1._item.id, dmap2._item.id)); - t.assert(compareIDs(dmap1._item.id, dmap3._item.id)); + t.assert(Y.compareIds(dmap1.itemId, dmap2.itemId)); + t.assert(Y.compareIds(dmap1.itemId, dmap3.itemId)); // @ts-ignore we want the possibility of dmapid being undefined t.assert(compareIDs(dmap1._item.id, dmapid)); compare(users); @@ -414,7 +387,6 @@ test.skip("testObserveDeepProperties", (t) => { test.skip("testObserversUsingObservedeep", (t) => { const { users, map0 } = init(gen, { users: 2 }); - assert(map0); const pathes: Array> = []; let calls = 0; @@ -425,8 +397,8 @@ test.skip("testObserversUsingObservedeep", (t) => { calls++; }); map0.set("map", new Y.Map()); - map0.get("map").set("array", new Y.Array()); - map0.get("map").get("array").insert(0, ["content"]); + map0.get("map").set("array", new Y.Array()); + map0.get("map").get("array").insert(0, ["content"]); t.assert(calls === 3); t.deepEqual(pathes, [[], ["map"], ["map", "array"]]); compare(users); @@ -434,13 +406,12 @@ test.skip("testObserversUsingObservedeep", (t) => { test.skip("testPathsOfSiblingEvents", (t) => { const { users, map0 } = init(gen, { users: 2 }); - assert(map0); const pathes: Array> = []; let calls = 0; const doc = users[0]; - map0.set("map", new Y.Map()); - map0.get("map").set("text1", new Y.Text("initial")); + map0.set("map", users[0].createMap()); + map0.get("map").set("text1", users[0].createText("initial")); map0.observeDeep((events) => { events.forEach((event) => { pathes.push(event.path); @@ -448,8 +419,8 @@ test.skip("testPathsOfSiblingEvents", (t) => { calls++; }); doc.transact(() => { - map0.get("map").get("text1").insert(0, "post-"); - map0.get("map").set("text2", new Y.Text("new")); + map0.get("map").get("text1").insert(0, "post-"); + map0.get("map").set("text2", users[0].createText("new")); }); t.assert(calls === 1); t.deepEqual(pathes, [["map"], ["map", "text1"]]); @@ -594,7 +565,7 @@ test.skip("testYmapEventExceptionsShouldCompleteTransaction", (t) => { let updateCalled = false; let throwingObserverCalled = false; let throwingDeepObserverCalled = false; - doc.on("update", () => { + doc.onUpdate(() => { updateCalled = true; }); @@ -636,7 +607,6 @@ test.skip("testYmapEventExceptionsShouldCompleteTransaction", (t) => { test.skip("testYmapEventHasCorrectValueWhenSettingAPrimitive", (t) => { const { users, map0 } = init(gen, { users: 3 }); - assert(map0); /** * @type {Object} @@ -652,8 +622,6 @@ test.skip("testYmapEventHasCorrectValueWhenSettingAPrimitive", (t) => { test.skip("testYmapEventHasCorrectValueWhenSettingAPrimitiveFromOtherUser", (t) => { const { users, map0, map1, testConnector } = init(gen, { users: 3 }); - assert(map0); - assert(map1); let event: Record = {}; map0.observe((e) => { @@ -688,68 +656,68 @@ const mapTransactions: Array<(arg0: Y.Doc, arg1: prng.PRNG) => void> = [ ]; test.skip("testRepeatGeneratingYmapTests10", (t) => { - applyRandomTests(tc, mapTransactions, 3); + applyRandomTests(gen, mapTransactions, 3); }); test.skip("testRepeatGeneratingYmapTests40", (t) => { - applyRandomTests(tc, mapTransactions, 40); + applyRandomTests(gen, mapTransactions, 40); }); test.skip("testRepeatGeneratingYmapTests42", (t) => { - applyRandomTests(tc, mapTransactions, 42); + applyRandomTests(gen, mapTransactions, 42); }); test.skip("testRepeatGeneratingYmapTests43", (t) => { - applyRandomTests(tc, mapTransactions, 43); + applyRandomTests(gen, mapTransactions, 43); }); test.skip("testRepeatGeneratingYmapTests44", (t) => { - applyRandomTests(tc, mapTransactions, 44); + applyRandomTests(gen, mapTransactions, 44); }); test.skip("testRepeatGeneratingYmapTests45", (t) => { - applyRandomTests(tc, mapTransactions, 45); + applyRandomTests(gen, mapTransactions, 45); }); test.skip("testRepeatGeneratingYmapTests46", (t) => { - applyRandomTests(tc, mapTransactions, 46); + applyRandomTests(gen, mapTransactions, 46); }); test.skip("testRepeatGeneratingYmapTests300", (t) => { - applyRandomTests(tc, mapTransactions, 300); + applyRandomTests(gen, mapTransactions, 300); }); test.skip("testRepeatGeneratingYmapTests400", (t) => { - applyRandomTests(tc, mapTransactions, 400); + applyRandomTests(gen, mapTransactions, 400); }); test.skip("testRepeatGeneratingYmapTests500", (t) => { - applyRandomTests(tc, mapTransactions, 500); + applyRandomTests(gen, mapTransactions, 500); }); test.skip("testRepeatGeneratingYmapTests600", (t) => { - applyRandomTests(tc, mapTransactions, 600); + applyRandomTests(gen, mapTransactions, 600); }); test.skip("testRepeatGeneratingYmapTests1000", (t) => { - applyRandomTests(tc, mapTransactions, 1000); + applyRandomTests(gen, mapTransactions, 1000); }); test.skip("testRepeatGeneratingYmapTests1800", (t) => { - applyRandomTests(tc, mapTransactions, 1800); + applyRandomTests(gen, mapTransactions, 1800); }); test.skip("testRepeatGeneratingYmapTests5000", (t) => { if (!production) return; - applyRandomTests(tc, mapTransactions, 5000); + applyRandomTests(gen, mapTransactions, 5000); }); test.skip("testRepeatGeneratingYmapTests10000", (t) => { if (!production) return; - applyRandomTests(tc, mapTransactions, 10000); + applyRandomTests(gen, mapTransactions, 10000); }); test.skip("testRepeatGeneratingYmapTests100000", (t) => { if (!production) return; - applyRandomTests(tc, mapTransactions, 100000); + applyRandomTests(gen, mapTransactions, 100000); }); diff --git a/y-octo-node/tests/yjs/y-text.spec.ts b/y-octo-node/tests/yjs/y-text.spec.ts index 94361f0..0be9b16 100644 --- a/y-octo-node/tests/yjs/y-text.spec.ts +++ b/y-octo-node/tests/yjs/y-text.spec.ts @@ -1660,8 +1660,6 @@ test.skip("testDeltaBug2", (t) => { */ test.skip("testDeltaAfterConcurrentFormatting", (t) => { const { text0, text1, testConnector } = init(gen, { users: 2 }); - assert(text0); - assert(text1); text0.insert(0, "abcde"); testConnector.flushAllMessages(); text0.format(0, 3, { bold: true }); @@ -1686,8 +1684,6 @@ test.skip("testDeltaAfterConcurrentFormatting", (t) => { test.skip("testBasicInsertAndDelete", (t) => { const { users, text0 } = init(gen, { users: 2 }); - assert(text0); - let delta; text0.observe((event) => { @@ -1720,8 +1716,6 @@ test.skip("testBasicInsertAndDelete", (t) => { test.skip("testBasicFormat", (t) => { const { users, text0 } = init(gen, { users: 2 }); - assert(text0); - let delta; text0.observe((event) => { delta = event.delta; @@ -1772,8 +1766,6 @@ test.skip("testBasicFormat", (t) => { test.skip("testFalsyFormats", (t) => { const { users, text0 } = init(gen, { users: 2 }); - assert(text0); - let delta; text0.observe((event) => { delta = event.delta; @@ -1866,7 +1858,6 @@ test.skip("testPreserveAttributesThroughDelete", (t) => { test.skip("testGetDeltaWithEmbeds", (t) => { const { text0 } = init(gen, { users: 1 }); - assert(text0); text0.applyDelta([ { insert: { linebreak: "s" }, @@ -1906,8 +1897,6 @@ test.skip("testTypesAsEmbed", (t) => { test.skip("testSnapshot", (t) => { const { text0 } = init(gen, { users: 1 }); - assert(text0); - const doc0 = text0.doc; doc0.gc = false; text0.applyDelta([ @@ -1985,15 +1974,12 @@ test.skip("testSnapshotDeleteAfter", (t) => { test.skip("testToJson", (t) => { const { text0 } = init(gen, { users: 1 }); - assert(text0); text0.insert(0, "abc", { bold: true }); t.assert(text0.toJSON() === "abc", "toJSON returns the unformatted text"); }); test.skip("testToDeltaEmbedAttributes", (t) => { const { text0 } = init(gen, { users: 1 }); - assert(text0); - text0.insert(0, "ab", { bold: true }); text0.insertEmbed(1, { image: "imageSrc.png" }, { width: 100 }); const delta0 = text0.toDelta(); @@ -2006,8 +1992,6 @@ test.skip("testToDeltaEmbedAttributes", (t) => { test.skip("testToDeltaEmbedNoAttributes", (t) => { const { text0 } = init(gen, { users: 1 }); - assert(text0); - text0.insert(0, "ab", { bold: true }); text0.insertEmbed(1, { image: "imageSrc.png" }); const delta0 = text0.toDelta(); @@ -2024,8 +2008,6 @@ test.skip("testToDeltaEmbedNoAttributes", (t) => { test.skip("testFormattingRemoved", (t) => { const { text0 } = init(gen, { users: 1 }); - assert(text0); - text0.insert(0, "ab", { bold: true }); text0.delete(0, 2); t.assert(Y.getTypeChildren(text0).length === 1); @@ -2075,8 +2057,6 @@ test.skip("testFormattingDeltaUnnecessaryAttributeChange", (t) => { test.skip("testInsertAndDeleteAtRandomPositions", (t) => { const N = 100000; const { text0 } = init(gen, { users: 1 }); - assert(text0); - // create initial content // let expectedResult = init text0.insert(0, prng.word(gen, N / 2, N / 2)); @@ -2102,8 +2082,6 @@ test.skip("testInsertAndDeleteAtRandomPositions", (t) => { test.skip("testAppendChars", (t) => { const N = 10000; const { text0 } = init(gen, { users: 1 }); - assert(text0); - // apply changes for (let i = 0; i < N; i++) { text0.insert(text0.length, "a"); @@ -2237,7 +2215,6 @@ test.skip("testIncrementalUpdatesPerformanceOnLargeFragmentedDocument", (t) => { test.skip("testSplitSurrogateCharacter", (t) => { { const { users, text0 } = init(gen, { users: 2 }); - assert(text0); users[1].disconnect(); // disconnecting forces the user to encode the split surrogate text0.insert(0, "👾"); // insert surrogate character // split surrogate, which should not lead to an encoding error @@ -2246,7 +2223,6 @@ test.skip("testSplitSurrogateCharacter", (t) => { } { const { users, text0 } = init(gen, { users: 2 }); - assert(text0); users[1].disconnect(); // disconnecting forces the user to encode the split surrogate text0.insert(0, "👾👾"); // insert surrogate character // partially delete surrogate @@ -2255,7 +2231,6 @@ test.skip("testSplitSurrogateCharacter", (t) => { } { const { users, text0 } = init(gen, { users: 2 }); - assert(text0); users[1].disconnect(); // disconnecting forces the user to encode the split surrogate text0.insert(0, "👾👾"); // insert surrogate character // formatting will also split surrogates @@ -2269,8 +2244,6 @@ test.skip("testSplitSurrogateCharacter", (t) => { */ test.skip("testSearchMarkerBug1", (t) => { const { users, text0, text1, testConnector } = init(gen, { users: 2 }); - assert(text0); - users[0].onUpdate((update) => { users[0].transact(() => { Y.applyUpdate(users[0], update); diff --git a/y-octo/src/doc/types/map.rs b/y-octo/src/doc/types/map.rs index c2336f1..b17c5a5 100644 --- a/y-octo/src/doc/types/map.rs +++ b/y-octo/src/doc/types/map.rs @@ -9,6 +9,10 @@ use crate::{ impl_type!(Map); pub(crate) trait MapType: AsInner { + fn _id(&self) -> Option { + self.as_inner().ty().and_then(|ty| ty.item.get().map(|item| item.id)) + } + fn _insert>(&mut self, key: String, value: V) -> JwstCodecResult { if let Some((mut store, mut ty)) = self.as_inner().write() { let left = ty.map.get(&SmolStr::new(&key)).cloned(); @@ -154,6 +158,11 @@ impl<'a> Iterator for EntriesIterator<'a> { impl MapType for Map {} impl Map { + #[inline(always)] + pub fn id(&self) -> Option { + self._id() + } + #[inline(always)] pub fn insert>(&mut self, key: String, value: V) -> JwstCodecResult { self._insert(key, value) From 097c82f3f3e3f46398a283391be24ae257532335 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Mon, 8 Jul 2024 16:07:58 +0800 Subject: [PATCH 20/75] feat: improve create text & map error handle --- y-octo-node/index.d.ts | 2 +- y-octo-node/src/doc.rs | 11 +++++----- y-octo-node/src/map.rs | 49 ++++++++++++++++-------------------------- 3 files changed, 25 insertions(+), 37 deletions(-) diff --git a/y-octo-node/index.d.ts b/y-octo-node/index.d.ts index c92064e..a3f818a 100644 --- a/y-octo-node/index.d.ts +++ b/y-octo-node/index.d.ts @@ -45,7 +45,7 @@ export class Doc { getOrCreateText(key: string): YText getOrCreateMap(key: string): YMap createArray(): YArray - createText(): YText + createText(text?: string | undefined | null): YText createMap(): YMap applyUpdate(update: Buffer): void encodeStateAsUpdateV1(state?: Buffer | undefined | null): Buffer diff --git a/y-octo-node/src/doc.rs b/y-octo-node/src/doc.rs index dafb101..aac3c84 100644 --- a/y-octo-node/src/doc.rs +++ b/y-octo-node/src/doc.rs @@ -82,11 +82,12 @@ impl YDoc { } #[napi] - pub fn create_text(&self) -> Result { - self.doc - .create_text() - .map(YText::inner_new) - .map_err(anyhow::Error::from) + pub fn create_text(&self, text: Option) -> Result { + let mut ytext = self.doc.create_text().map(YText::inner_new)?; + if let Some(text) = text { + ytext.insert(0, text)?; + } + Ok(ytext) } #[napi] diff --git a/y-octo-node/src/map.rs b/y-octo-node/src/map.rs index 327a6be..c60aa23 100644 --- a/y-octo-node/src/map.rs +++ b/y-octo-node/src/map.rs @@ -29,6 +29,11 @@ impl YMap { self.map.is_empty() } + #[napi(getter)] + pub fn item_id(&self) -> Option { + self.map.id().map(|id| YId { id }) + } + #[napi(ts_generic_types = "T = unknown", ts_return_type = "T")] pub fn get(&self, env: Env, key: String) -> Result { if let Some(value) = self.map.get(&key) { @@ -53,44 +58,34 @@ impl YMap { pub fn set(&mut self, env: Env, key: String, value: MixedRefYType) -> Result { match value { MixedRefYType::A(array) => { - self.map.insert(key, array.array.clone()).map_err(anyhow::Error::from)?; + self.map.insert(key, array.array.clone())?; Ok(MixedYType::A(YArray::inner_new(array.array.clone()))) } MixedRefYType::B(map) => { - self.map.insert(key, map.map.clone()).map_err(anyhow::Error::from)?; + self.map.insert(key, map.map.clone())?; Ok(MixedYType::B(YMap::inner_new(map.map.clone()))) } MixedRefYType::C(text) => { - self.map.insert(key, text.text.clone()).map_err(anyhow::Error::from)?; + self.map.insert(key, text.text.clone())?; Ok(MixedYType::C(YText::inner_new(text.text.clone()))) } MixedRefYType::D(unknown) => match unknown.get_type() { Ok(value_type) => match value_type { ValueType::Undefined | ValueType::Null => { - self.map.insert(key, Any::Null).map_err(anyhow::Error::from)?; - Ok(MixedYType::D( - env.get_null().map(|v| v.into_unknown()).map_err(anyhow::Error::from)?, - )) + self.map.insert(key, Any::Null)?; + Ok(MixedYType::D(env.get_null().map(|v| v.into_unknown())?)) } ValueType::Boolean => match unknown.coerce_to_bool().and_then(|v| v.get_value()) { Ok(boolean) => { - self.map.insert(key, boolean).map_err(anyhow::Error::from)?; - Ok(MixedYType::D( - env.get_boolean(boolean) - .map(|v| v.into_unknown()) - .map_err(anyhow::Error::from)?, - )) + self.map.insert(key, boolean)?; + Ok(MixedYType::D(env.get_boolean(boolean).map(|v| v.into_unknown())?)) } Err(e) => Err(anyhow::Error::from(e).context("Failed to coerce value to boolean")), }, ValueType::Number => match unknown.coerce_to_number().and_then(|v| v.get_double()) { Ok(number) => { - self.map.insert(key, number).map_err(anyhow::Error::from)?; - Ok(MixedYType::D( - env.create_double(number) - .map(|v| v.into_unknown()) - .map_err(anyhow::Error::from)?, - )) + self.map.insert(key, number)?; + Ok(MixedYType::D(env.create_double(number).map(|v| v.into_unknown())?)) } Err(e) => Err(anyhow::Error::from(e).context("Failed to coerce value to number")), }, @@ -101,24 +96,16 @@ impl YMap { .and_then(|s| s.as_str().map(|s| s.to_string())) { Ok(string) => { - self.map.insert(key, string.clone()).map_err(anyhow::Error::from)?; - Ok(MixedYType::D( - env.create_string(&string) - .map(|v| v.into_unknown()) - .map_err(anyhow::Error::from)?, - )) + self.map.insert(key, string.clone())?; + Ok(MixedYType::D(env.create_string(&string).map(|v| v.into_unknown())?)) } Err(e) => Err(anyhow::Error::from(e).context("Failed to coerce value to string")), } } ValueType::Object => match unknown.coerce_to_object().and_then(get_any_from_js_object) { Ok(any) => { - self.map - .insert(key, Value::Any(any.clone())) - .map_err(anyhow::Error::from)?; - Ok(MixedYType::D( - get_js_unknown_from_any(env, any).map_err(anyhow::Error::from)?.into(), - )) + self.map.insert(key, Value::Any(any.clone()))?; + Ok(MixedYType::D(get_js_unknown_from_any(env, any)?.into())) } Err(e) => Err(anyhow::Error::from(e).context("Failed to coerce value to object")), }, From fc4120b8c8fe3e4f08e4cd1fe75fceb4e5de5a0e Mon Sep 17 00:00:00 2001 From: DarkSky Date: Mon, 8 Jul 2024 17:28:29 +0800 Subject: [PATCH 21/75] feat: cascade Insert correctly --- y-octo-node/src/utils.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/y-octo-node/src/utils.rs b/y-octo-node/src/utils.rs index 366fb74..ef1f4fc 100644 --- a/y-octo-node/src/utils.rs +++ b/y-octo-node/src/utils.rs @@ -23,6 +23,13 @@ pub fn get_js_unknown_from_any(env: Env, any: Any) -> Result { } Ok(js_array.into_unknown()) } + Any::Object(object) => { + let mut js_object = env.create_object()?; + for (key, value) in object.into_iter() { + js_object.set_named_property(&key, get_js_unknown_from_any(env, value)?)?; + } + Ok(js_object.into_unknown()) + } _ => env.get_null().map(|v| v.into_unknown()), } } From b123a45c008fc265851593261e3002322680c816 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Tue, 9 Jul 2024 12:08:05 +0800 Subject: [PATCH 22/75] feat: map api simulate --- y-octo-node/index.d.ts | 6 +++++- y-octo-node/index.js | 3 ++- y-octo-node/src/doc.rs | 23 ++++++++++++++++++--- y-octo-node/src/map.rs | 45 +++++++++++++++++++++++++++++++++++++++++- 4 files changed, 71 insertions(+), 6 deletions(-) diff --git a/y-octo-node/index.d.ts b/y-octo-node/index.d.ts index a3f818a..f4abc20 100644 --- a/y-octo-node/index.d.ts +++ b/y-octo-node/index.d.ts @@ -46,7 +46,7 @@ export class Doc { getOrCreateMap(key: string): YMap createArray(): YArray createText(text?: string | undefined | null): YText - createMap(): YMap + createMap(entries?: JsArray | undefined | null): YMap applyUpdate(update: Buffer): void encodeStateAsUpdateV1(state?: Buffer | undefined | null): Buffer gc(): void @@ -65,6 +65,7 @@ export class YMap { toJson(): object entries(): YMapEntriesIterator keys(): YMapKeyIterator + values(): YMapValuesIterator observe(callback: (...args: any[]) => any): void observeDeep(callback: (...args: any[]) => any): void } @@ -74,6 +75,9 @@ export class YMapEntriesIterator { export class YMapKeyIterator { [Symbol.iterator](): Iterator } +export class YMapValuesIterator { + [Symbol.iterator](): Iterator +} export class YText { constructor() get len(): number diff --git a/y-octo-node/index.js b/y-octo-node/index.js index 81cc47d..37bd775 100644 --- a/y-octo-node/index.js +++ b/y-octo-node/index.js @@ -252,7 +252,7 @@ if (!nativeBinding) { throw new Error(`Failed to load native binding`) } -const { YArray, YArrayIterator, Doc, encodeStateAsUpdate, encodeStateVector, compareStructStores, compareIds, createDeleteSetFromStructStore, equalDeleteSets, snapshot, encodeSnapshot, applyUpdate, mergeUpdates, isAbstractType, YMap, YMapEntriesIterator, YMapKeyIterator, YText, Id, Store, DeleteSet, YSnapshot } = nativeBinding +const { YArray, YArrayIterator, Doc, encodeStateAsUpdate, encodeStateVector, compareStructStores, compareIds, createDeleteSetFromStructStore, equalDeleteSets, snapshot, encodeSnapshot, applyUpdate, mergeUpdates, isAbstractType, YMap, YMapEntriesIterator, YMapKeyIterator, YMapValuesIterator, YText, Id, Store, DeleteSet, YSnapshot } = nativeBinding module.exports.YArray = YArray module.exports.YArrayIterator = YArrayIterator @@ -271,6 +271,7 @@ module.exports.isAbstractType = isAbstractType module.exports.YMap = YMap module.exports.YMapEntriesIterator = YMapEntriesIterator module.exports.YMapKeyIterator = YMapKeyIterator +module.exports.YMapValuesIterator = YMapValuesIterator module.exports.YText = YText module.exports.Id = Id module.exports.Store = Store diff --git a/y-octo-node/src/doc.rs b/y-octo-node/src/doc.rs index aac3c84..c709cf7 100644 --- a/y-octo-node/src/doc.rs +++ b/y-octo-node/src/doc.rs @@ -1,6 +1,7 @@ use napi::{ - bindgen_prelude::{Buffer as JsBuffer, JsFunction}, + bindgen_prelude::{Array as JsArray, Buffer as JsBuffer, JsFunction}, threadsafe_function::{ErrorStrategy, ThreadsafeFunction, ThreadsafeFunctionCallMode}, + Env, JsString, JsUnknown, }; use y_octo::{CrdtRead, Doc, History, RawDecoder, StateVector}; @@ -91,8 +92,24 @@ impl YDoc { } #[napi] - pub fn create_map(&self) -> Result { - self.doc.create_map().map(YMap::inner_new).map_err(anyhow::Error::from) + pub fn create_map(&self, env: Env, entries: Option) -> Result { + let mut ymap = self.doc.create_map().map(YMap::inner_new)?; + if let Some(entries) = entries { + for i in 0..entries.len() { + if let Ok(Some(value)) = entries.get::(i) { + let key = value.get::(0)?; + let value = value.get::(1)?; + if let (Some(key), Some(value)) = (key, value) { + ymap.set(env, key.into_utf8()?.into_owned()?, MixedRefYType::D(value))?; + continue; + } + } + + return Err(anyhow::anyhow!("Invalid entry")); + } + } + + Ok(ymap) } #[napi] diff --git a/y-octo-node/src/map.rs b/y-octo-node/src/map.rs index c60aa23..2069cb2 100644 --- a/y-octo-node/src/map.rs +++ b/y-octo-node/src/map.rs @@ -1,4 +1,4 @@ -use napi::{bindgen_prelude::Array as JsArray, iterator::Generator, Env, JsFunction, JsObject, ValueType}; +use napi::{bindgen_prelude::Array as JsArray, iterator::Generator, Env, JsFunction, JsObject, JsUnknown, ValueType}; use y_octo::{Any, Map, Value}; use super::*; @@ -158,6 +158,15 @@ impl YMap { } } + #[napi] + pub fn values(&self, env: Env) -> YMapValuesIterator { + YMapValuesIterator { + entries: self.map.iter().map(|(_, v)| v).collect(), + env, + current: 0, + } + } + // TODO(@darkskygit): impl type based observe #[napi] pub fn observe(&mut self, _callback: JsFunction) -> Result<()> { @@ -239,6 +248,40 @@ impl Generator for YMapKeyIterator { } } +#[napi(iterator)] +pub struct YMapValuesIterator { + entries: Vec, + env: Env, + current: i64, +} + +#[napi] +impl Generator for YMapValuesIterator { + type Yield = JsUnknown; + + type Next = Option; + + type Return = (); + + fn next(&mut self, value: Option) -> Option { + let current = self.current as usize; + if self.entries.len() <= current { + return None; + } + let ret = if let Some(value) = self.entries.get(current) { + get_js_unknown_from_value(self.env, value.clone()).ok() + } else { + None + }; + self.current = if let Some(value) = value.and_then(|v| v) { + value + } else { + self.current + 1 + }; + ret + } +} + #[cfg(test)] mod tests { From b60c673acc726f5b1619cb161628abab3bcf9263 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Tue, 9 Jul 2024 15:23:34 +0800 Subject: [PATCH 23/75] feat: fix more tests --- y-octo-node/tests/yjs/testHelper.ts | 51 ++++++++++++++--------------- y-octo-node/tests/yjs/y-map.spec.ts | 28 ++++++++-------- 2 files changed, 39 insertions(+), 40 deletions(-) diff --git a/y-octo-node/tests/yjs/testHelper.ts b/y-octo-node/tests/yjs/testHelper.ts index 778a9e7..4a6b9e8 100644 --- a/y-octo-node/tests/yjs/testHelper.ts +++ b/y-octo-node/tests/yjs/testHelper.ts @@ -1,4 +1,4 @@ -import * as t from "lib0/testing"; +import assert, { deepEqual } from "node:assert"; import * as prng from "lib0/prng"; import * as encoding from "lib0/encoding"; import * as decoding from "lib0/decoding"; @@ -75,14 +75,14 @@ export class TestYOctoInstance extends Y.Doc { testConnector.allConns.add(this); this.updates = []; // set up observe on local model - this.onUpdate((update) => { - if (origin !== testConnector) { - const encoder = encoding.createEncoder(); - syncProtocol.writeUpdate(encoder, update); - broadcastMessage(this, encoding.toUint8Array(encoder)); - } - this.updates.push(update); - }); + // this.onUpdate((update) => { + // // if (origin !== testConnector) { + // // const encoder = encoding.createEncoder(); + // // syncProtocol.writeUpdate(encoder, update); + // // broadcastMessage(this, encoding.toUint8Array(encoder)); + // // } + // this.updates.push(update); + // }); this.connect(); } @@ -154,6 +154,7 @@ export class TestConnector { * If this function was unable to flush a message, because there are no more messages to flush, it returns false. true otherwise. */ flushRandomMessage(): boolean { + return false; const gen = this.prng; const conns = Array.from(this.onlineConns).filter( (conn) => conn.receiving.size > 0, @@ -201,13 +202,11 @@ export class TestConnector { } reconnectAll() { - this.allConns.forEach((conn: { connect: () => any }) => conn.connect()); + this.allConns.forEach((conn) => conn.connect()); } disconnectAll() { - this.allConns.forEach((conn: { disconnect: () => any }) => - conn.disconnect(), - ); + this.allConns.forEach((conn) => conn.disconnect()); } syncAll() { @@ -330,32 +329,32 @@ export const compare = (users: TestYOctoInstance[]) => { // t.assert(u.store.pendingStructs === null); // } // Test Array iterator - t.compare( + deepEqual( users[0].getOrCreateArray("array").toArray(), Array.from(users[0].getOrCreateArray("array").iter()), ); // Test Map iterator const ymapkeys: any[] = Array.from(users[0].getOrCreateMap("map").keys()); - t.assert(ymapkeys.length === Object.keys(userMapValues[0]).length); + assert(ymapkeys.length === Object.keys(userMapValues[0]).length); ymapkeys.forEach((key) => - t.assert(object.hasProperty(userMapValues[0], key)), + assert(object.hasProperty(userMapValues[0], key)), ); const mapRes: Record = {}; for (const [k, v] of users[0].getOrCreateMap("map").entries()) { mapRes[k] = Y.isAbstractType(v) ? v.toJSON() : v; } - t.compare(userMapValues[0], mapRes); + deepEqual(userMapValues[0], mapRes); // Compare all users for (let i = 0; i < users.length - 1; i++) { - t.compare( + deepEqual( userArrayValues[i].length, users[i].getOrCreateArray("array").length, ); - t.compare(userArrayValues[i], userArrayValues[i + 1]); - t.compare(userMapValues[i], userMapValues[i + 1]); - // t.compare(userXmlValues[i], userXmlValues[i + 1]); - // t.compare( + deepEqual(userArrayValues[i], userArrayValues[i + 1]); + deepEqual(userMapValues[i], userMapValues[i + 1]); + // deepEqual(userXmlValues[i], userXmlValues[i + 1]); + // deepEqual( // userTextValues[i] // .map( // /** @param {any} a */ (a: { insert: any }) => @@ -364,26 +363,26 @@ export const compare = (users: TestYOctoInstance[]) => { // .join("").length, // users[i].getOrCreateText("text").length, // ); - // t.compare( + // deepEqual( // userTextValues[i], // userTextValues[i + 1], // "", // (_constructor, a, b) => { // if (Y.isAbstractType(a)) { - // t.compare(a.toJSON(), b.toJSON()); + // deepEqual(a.toJSON(), b.toJSON()); // } else if (a !== b) { // t.fail("Deltas dont match"); // } // return true; // }, // ); - t.compare(Y.encodeStateVector(users[i]), Y.encodeStateVector(users[i + 1])); + deepEqual(Y.encodeStateVector(users[i]), Y.encodeStateVector(users[i + 1])); Y.equalDeleteSets( Y.createDeleteSetFromStructStore(users[i].store), Y.createDeleteSetFromStructStore(users[i + 1].store), ); Y.compareStructStores(users[i].store, users[i + 1].store); - t.compare( + deepEqual( Y.encodeSnapshot(Y.snapshot(users[i])), Y.encodeSnapshot(Y.snapshot(users[i + 1])), ); diff --git a/y-octo-node/tests/yjs/y-map.spec.ts b/y-octo-node/tests/yjs/y-map.spec.ts index bea69f6..55f46fc 100644 --- a/y-octo-node/tests/yjs/y-map.spec.ts +++ b/y-octo-node/tests/yjs/y-map.spec.ts @@ -14,14 +14,16 @@ test.beforeEach(() => { gen = prng.create(randomInt(0, 0xffffffff)); }); -test.skip("testIterators", (t) => { +test("testIterators", (t) => { const ydoc = new Y.Doc(); const ymap = ydoc.createMap(); // we are only checking if the type assumptions are correct const vals = Array.from(ymap.values()); const entries = Array.from(ymap.entries()); const keys = Array.from(ymap.keys()); - console.log(vals, entries, keys); + t.is(vals.length, 0); + t.is(entries.length, 0); + t.is(keys.length, 0); }); /** @@ -30,22 +32,20 @@ test.skip("testIterators", (t) => { test.skip("testMapEventError", (t) => { const doc = new Y.Doc(); const ymap = doc.createMap(); - /** - * @type {any} - */ + let event: any = null; ymap.observe((e) => { event = e; }); - t.fails(() => { + t.throws(() => { t.info(event.keys); }); - t.fails(() => { + t.throws(() => { t.info(event.keys); }); }); -test.skip("testMapHavingIterableAsConstructorParamTests", (t) => { +test("testMapHavingIterableAsConstructorParamTests", (t) => { const { users, map0 } = init(gen, { users: 1 }); const m1 = users[0].createMap(Object.entries({ number: 1, string: "hello" })); @@ -58,14 +58,14 @@ test.skip("testMapHavingIterableAsConstructorParamTests", (t) => { ["boolean", true], ]); map0.set("m2", m2); - t.assert(m2.get("object").x === 1); + t.assert(m2.get("object").x === 1); t.assert(m2.get("boolean") === true); - const m3 = users[0].createMap([...m1, ...m2]); + const m3 = users[0].createMap([...m1.entries(), ...m2.entries()]); map0.set("m3", m3); t.assert(m3.get("number") === 1); t.assert(m3.get("string") === "hello"); - t.assert(m3.get("object").x === 1); + t.assert(m3.get("object").x === 1); t.assert(m3.get("boolean") === true); }); @@ -195,8 +195,8 @@ test.skip("testGetAndSetOfMapProperty", (t) => { test.skip("testYmapSetsYmap", (t) => { const { users, map0 } = init(gen, { users: 2 }); - const map = map0.set("Map", new Y.Map()); - t.assert(map0.get("Map") === map); + const map = map0.set("Map", users[0].createMap()); + t.assert(Y.compareIds(map0.get("Map").itemId, map.itemId)); map.set("one", 1); t.deepEqual(map.get("one"), 1); compare(users); @@ -239,7 +239,7 @@ test.skip("testGetAndSetOfMapPropertyWithConflict", (t) => { compare(users); }); -test.skip("testSizeAndDeleteOfMapProperty", (t) => { +test("testSizeAndDeleteOfMapProperty", (t) => { const { map0 } = init(gen, { users: 1 }); map0.set("stuff", "c0"); From 4bd4ec88ba69247c14f5f2f8a99efe463fedc0a5 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Tue, 9 Jul 2024 16:11:54 +0800 Subject: [PATCH 24/75] chore: organize y-protocol typing --- y-octo-node/y-protocol.d.ts | 126 ++++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 y-octo-node/y-protocol.d.ts diff --git a/y-octo-node/y-protocol.d.ts b/y-octo-node/y-protocol.d.ts new file mode 100644 index 0000000..3e6eba1 --- /dev/null +++ b/y-octo-node/y-protocol.d.ts @@ -0,0 +1,126 @@ +import * as encoding from "lib0/encoding"; +import * as decoding from "lib0/decoding"; +import { Observable } from "lib0/observable"; +import * as Y from "yjs"; + +export type AwarenessState = Record; +export type AwarenessStates = Map; + +export type MetaClientState = { + clock: number; + /** + * unix timestamp + */ + lastUpdated: number; +}; + +/** + * The Awareness class implements a simple shared state protocol that can be used for non-persistent data like awareness information + * (cursor, username, status, ..). Each client can update its own local state and listen to state changes of + * remote clients. Every client may set a state of a remote peer to `null` to mark the client as offline. + * + * Each client is identified by a unique client id (something we borrow from `doc.clientID`). A client can override + * its own state by propagating a message with an increasing timestamp (`clock`). If such a message is received, it is + * applied if the known state of that client is older than the new state (`clock < newClock`). If a client thinks that + * a remote client is offline, it may propagate a message with + * `{ clock: currentClientClock, state: null, client: remoteClient }`. If such a + * message is received, and the known clock of that client equals the received clock, it will override the state with `null`. + * + * Before a client disconnects, it should propagate a `null` state with an updated clock. + * + * Awareness states must be updated every 30 seconds. Otherwise the Awareness instance will delete the client state. + */ +export class Awareness extends Observable { + constructor(doc: Y.Doc); + doc: Y.Doc; + clientID: number; + /** + * Maps from client id to client state + */ + states: AwarenessStates; + meta: Map; + _checkInterval: any; + getLocalState(): AwarenessState | null; + setLocalState(state: AwarenessState | null): void; + setLocalStateField(field: string, value: any): void; + getStates(): AwarenessStates; +} + +export function removeAwarenessStates( + awareness: Awareness, + clients: Array, + origin: any, +): void; +export function encodeAwarenessUpdate( + awareness: Awareness, + clients: Array, + states?: AwarenessStates, +): Uint8Array; +export function modifyAwarenessUpdate( + update: Uint8Array, + modify: (arg0: any) => any, +): Uint8Array; +export function applyAwarenessUpdate( + awareness: Awareness, + update: Uint8Array, + origin: any, +): void; + +/** + * Core Yjs defines two message types: + * • YjsSyncStep1: Includes the State Set of the sending client. When received, the client should reply with YjsSyncStep2. + * • YjsSyncStep2: Includes all missing structs and the complete delete set. When received, the client is assured that it + * received all information from the remote client. + * + * In a peer-to-peer network, you may want to introduce a SyncDone message type. Both parties should initiate the connection + * with SyncStep1. When a client received SyncStep2, it should reply with SyncDone. When the local client received both + * SyncStep2 and SyncDone, it is assured that it is synced to the remote client. + * + * In a client-server model, you want to handle this differently: The client should initiate the connection with SyncStep1. + * When the server receives SyncStep1, it should reply with SyncStep2 immediately followed by SyncStep1. The client replies + * with SyncStep2 when it receives SyncStep1. Optionally the server may send a SyncDone after it received SyncStep2, so the + * client knows that the sync is finished. There are two reasons for this more elaborated sync model: 1. This protocol can + * easily be implemented on top of http and websockets. 2. The server should only reply to requests, and not initiate them. + * Therefore it is necessary that the client initiates the sync. + * + * Construction of a message: + * [messageType : varUint, message definition..] + * + * Note: A message does not include information about the room name. This must to be handled by the upper layer protocol! + * + * stringify[messageType] stringifies a message definition (messageType is already read from the bufffer) + */ +export type messageYjsSyncStep1 = 0; +export type messageYjsSyncStep2 = 1; +export type messageYjsUpdate = 2; +export function writeSyncStep1(encoder: encoding.Encoder, doc: Y.Doc): void; +export function writeSyncStep2( + encoder: encoding.Encoder, + doc: Y.Doc, + encodedStateVector?: Uint8Array | undefined, +): void; +export function readSyncStep1( + decoder: decoding.Decoder, + encoder: encoding.Encoder, + doc: Y.Doc, +): void; +export function readSyncStep2( + decoder: decoding.Decoder, + doc: Y.Doc, + transactionOrigin: any, +): void; +export function writeUpdate( + encoder: encoding.Encoder, + update: Uint8Array, +): void; +export function readUpdate( + decoder: decoding.Decoder, + doc: Y.Doc, + transactionOrigin: any, +): void; +export function readSyncMessage( + decoder: decoding.Decoder, + encoder: encoding.Encoder, + doc: Y.Doc, + transactionOrigin: any, +): messageYjsSyncStep1 | messageYjsSyncStep2 | messageYjsUpdate; From 8ec15a44cd32575b91cb3bdb4f905d1b858681f4 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Tue, 9 Jul 2024 17:56:27 +0800 Subject: [PATCH 25/75] chore: move `prefer_small_random` --- y-octo/src/doc/document.rs | 18 ------------------ y-octo/src/doc/utils.rs | 18 ++++++++++++++++++ y-octo/src/lib.rs | 8 ++++---- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/y-octo/src/doc/document.rs b/y-octo/src/doc/document.rs index 72db532..b94e0d5 100644 --- a/y-octo/src/doc/document.rs +++ b/y-octo/src/doc/document.rs @@ -40,24 +40,6 @@ impl Default for DocOptions { gc: true, } } else { - /// It tends to generate small numbers. - /// Since the client id will be included in all crdt items, the - /// small client helps to reduce the binary size. - /// - /// NOTE: The probability of 36% of the random number generated by - /// this function is greater than [u32::MAX] - fn prefer_small_random() -> u64 { - use rand::{distributions::Distribution, thread_rng}; - use rand_distr::Exp; - - let scale_factor = u16::MAX as f64; - let v: f64 = Exp::new(1.0 / scale_factor) - .map(|exp| exp.sample(&mut thread_rng())) - .unwrap_or_else(|_| rand::random()); - - (v * scale_factor) as u64 - } - Self { client_id: prefer_small_random(), guid: nanoid::nanoid!(), diff --git a/y-octo/src/doc/utils.rs b/y-octo/src/doc/utils.rs index 7463d38..1c52fa6 100644 --- a/y-octo/src/doc/utils.rs +++ b/y-octo/src/doc/utils.rs @@ -24,3 +24,21 @@ pub fn merge_updates_v1, I: IntoIterator>(updates: I) - Ok(Update::merge(updates)) } + +/// It tends to generate small numbers. +/// Since the client id will be included in all crdt items, the +/// small client helps to reduce the binary size. +/// +/// NOTE: The probability of 36% of the random number generated by +/// this function is greater than [u32::MAX] +pub fn prefer_small_random() -> u64 { + use rand::{distributions::Distribution, thread_rng}; + use rand_distr::Exp; + + let scale_factor = u16::MAX as f64; + let v: f64 = Exp::new(1.0 / scale_factor) + .map(|exp| exp.sample(&mut thread_rng())) + .unwrap_or_else(|_| rand::random()); + + (v * scale_factor) as u64 +} diff --git a/y-octo/src/lib.rs b/y-octo/src/lib.rs index 9f737eb..443e922 100644 --- a/y-octo/src/lib.rs +++ b/y-octo/src/lib.rs @@ -6,10 +6,10 @@ mod sync; pub use codec::*; pub use doc::{ - encode_awareness_as_message, encode_update_as_message, merge_updates_v1, Any, Array, Awareness, AwarenessEvent, - Client, ClientMap, Clock, CrdtRead, CrdtReader, CrdtWrite, CrdtWriter, DeleteSet, Doc, DocOptions, EntriesIterator, - HashMap as AHashMap, HashMapExt, History, HistoryOptions, Id, KeysIterator, Map, RawDecoder, RawEncoder, - StateVector, StoreHistory, Text, Update, Value, ValuesIterator, + encode_awareness_as_message, encode_update_as_message, merge_updates_v1, prefer_small_random, Any, Array, + Awareness, AwarenessEvent, Client, ClientMap, Clock, CrdtRead, CrdtReader, CrdtWrite, CrdtWriter, DeleteSet, Doc, + DocOptions, EntriesIterator, HashMap as AHashMap, HashMapExt, History, HistoryOptions, Id, KeysIterator, Map, + RawDecoder, RawEncoder, StateVector, StoreHistory, Text, Update, Value, ValuesIterator, }; pub(crate) use doc::{Content, Item}; use log::{debug, warn}; From 3567b6478f680254d7cbff7a2b19dc15e49bef7c Mon Sep 17 00:00:00 2001 From: DarkSky Date: Thu, 11 Jul 2024 11:56:42 +0800 Subject: [PATCH 26/75] feat: init awaress binding --- y-octo-node/src/awareness.rs | 26 ++++++++++++++++++++++++++ y-octo-node/src/lib.rs | 2 ++ 2 files changed, 28 insertions(+) create mode 100644 y-octo-node/src/awareness.rs diff --git a/y-octo-node/src/awareness.rs b/y-octo-node/src/awareness.rs new file mode 100644 index 0000000..fc361a1 --- /dev/null +++ b/y-octo-node/src/awareness.rs @@ -0,0 +1,26 @@ +use napi::{ + bindgen_prelude::{Array as JsArray, Buffer as JsBuffer, JsFunction}, + threadsafe_function::{ErrorStrategy, ThreadsafeFunction, ThreadsafeFunctionCallMode}, + Env, JsString, JsUnknown, +}; +use y_octo::{prefer_small_random, Awareness, CrdtRead, Doc, History, RawDecoder, StateVector}; + +use super::*; + +#[napi(js_name = "Awareness")] +pub struct YAwareness { + pub(crate) awareness: Awareness, +} + +#[napi] +impl YAwareness { + #[napi(constructor)] + pub fn new(client_id: Option) -> Self { + let client_id = client_id + .and_then(|c| c.try_into().ok()) + .unwrap_or_else(|| prefer_small_random()); + Self { + awareness: Awareness::new(client_id as u64), + } + } +} diff --git a/y-octo-node/src/lib.rs b/y-octo-node/src/lib.rs index 0310c78..56ea290 100644 --- a/y-octo-node/src/lib.rs +++ b/y-octo-node/src/lib.rs @@ -2,6 +2,7 @@ use anyhow::Result; use napi_derive::napi; mod array; +mod awareness; mod doc; mod function; mod map; @@ -10,6 +11,7 @@ mod types; mod utils; pub use array::*; +pub use awareness::*; pub use doc::*; pub use function::*; pub use map::*; From dda49f795779cec43b07a237f64d029b6b41be96 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Thu, 11 Jul 2024 14:35:47 +0800 Subject: [PATCH 27/75] feat: awareness states --- y-octo-node/Cargo.toml | 3 ++- y-octo-node/index.d.ts | 6 ++++++ y-octo-node/index.js | 3 ++- y-octo-node/src/awareness.rs | 27 +++++++++++++++++++++------ y-octo/src/doc/awareness.rs | 4 ++++ 5 files changed, 35 insertions(+), 8 deletions(-) diff --git a/y-octo-node/Cargo.toml b/y-octo-node/Cargo.toml index 69ba858..e1b2d72 100644 --- a/y-octo-node/Cargo.toml +++ b/y-octo-node/Cargo.toml @@ -12,8 +12,9 @@ crate-type = ["cdylib"] [dependencies] anyhow = "1" -napi = { version = "2", features = ["anyhow", "napi4"] } +napi = { version = "2", features = ["anyhow", "napi4", "serde-json"] } napi-derive = "2" +serde_json = "1.0" y-octo = { workspace = true, features = ["large_refs"] } [build-dependencies] diff --git a/y-octo-node/index.d.ts b/y-octo-node/index.d.ts index f4abc20..254b3a4 100644 --- a/y-octo-node/index.d.ts +++ b/y-octo-node/index.d.ts @@ -33,6 +33,12 @@ export class YArray { export class YArrayIterator { [Symbol.iterator](): Iterator } +export type YAwareness = Awareness +export class Awareness { + constructor(clientId?: number | undefined | null) + get clientId(): number + get states>(): Record +} export type YDoc = Doc export class Doc { constructor(clientId?: number | undefined | null) diff --git a/y-octo-node/index.js b/y-octo-node/index.js index 37bd775..5716a51 100644 --- a/y-octo-node/index.js +++ b/y-octo-node/index.js @@ -252,10 +252,11 @@ if (!nativeBinding) { throw new Error(`Failed to load native binding`) } -const { YArray, YArrayIterator, Doc, encodeStateAsUpdate, encodeStateVector, compareStructStores, compareIds, createDeleteSetFromStructStore, equalDeleteSets, snapshot, encodeSnapshot, applyUpdate, mergeUpdates, isAbstractType, YMap, YMapEntriesIterator, YMapKeyIterator, YMapValuesIterator, YText, Id, Store, DeleteSet, YSnapshot } = nativeBinding +const { YArray, YArrayIterator, Awareness, Doc, encodeStateAsUpdate, encodeStateVector, compareStructStores, compareIds, createDeleteSetFromStructStore, equalDeleteSets, snapshot, encodeSnapshot, applyUpdate, mergeUpdates, isAbstractType, YMap, YMapEntriesIterator, YMapKeyIterator, YMapValuesIterator, YText, Id, Store, DeleteSet, YSnapshot } = nativeBinding module.exports.YArray = YArray module.exports.YArrayIterator = YArrayIterator +module.exports.Awareness = Awareness module.exports.Doc = Doc module.exports.encodeStateAsUpdate = encodeStateAsUpdate module.exports.encodeStateVector = encodeStateVector diff --git a/y-octo-node/src/awareness.rs b/y-octo-node/src/awareness.rs index fc361a1..5418817 100644 --- a/y-octo-node/src/awareness.rs +++ b/y-octo-node/src/awareness.rs @@ -1,9 +1,5 @@ -use napi::{ - bindgen_prelude::{Array as JsArray, Buffer as JsBuffer, JsFunction}, - threadsafe_function::{ErrorStrategy, ThreadsafeFunction, ThreadsafeFunctionCallMode}, - Env, JsString, JsUnknown, -}; -use y_octo::{prefer_small_random, Awareness, CrdtRead, Doc, History, RawDecoder, StateVector}; +use napi::{bindgen_prelude::Object as JsObject, Env}; +use y_octo::{prefer_small_random, Awareness}; use super::*; @@ -23,4 +19,23 @@ impl YAwareness { awareness: Awareness::new(client_id as u64), } } + + #[napi(getter)] + pub fn client_id(&self) -> i64 { + self.awareness.local_id() as i64 + } + + #[napi( + getter, + ts_generic_types = "T = Record", + ts_return_type = "Record" + )] + pub fn states(&self, env: Env) -> Result { + let mut object = env.create_object()?; + for (k, v) in self.awareness.get_states() { + let value = env.to_js_value(&serde_json::from_str(&v.content())?)?; + object.set_named_property(&k.to_string(), value)?; + } + Ok(object) + } } diff --git a/y-octo/src/doc/awareness.rs b/y-octo/src/doc/awareness.rs index fcbb75a..63cf32b 100644 --- a/y-octo/src/doc/awareness.rs +++ b/y-octo/src/doc/awareness.rs @@ -20,6 +20,10 @@ impl Awareness { } } + pub fn local_id(&self) -> u64 { + self.local_id + } + pub fn on_update(&mut self, f: impl Fn(&Awareness, AwarenessEvent) + Send + Sync + 'static) { self.callback = Some(Arc::new(f)); } From 7de07e239f7111e24513f9ab46ec05d28a3a0024 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Mon, 19 Aug 2024 14:40:03 +0800 Subject: [PATCH 28/75] chore: update deps --- package.json | 83 ++++++++--------- y-octo-node/package.json | 4 +- y-octo-node/src/array.rs | 6 +- y-octo-node/src/lib.rs | 1 - y-octo-node/src/map.rs | 3 +- y-octo-node/tests/yjs/testHelper.ts | 4 +- yarn.lock | 134 ++++++++++++++++------------ 7 files changed, 127 insertions(+), 108 deletions(-) diff --git a/package.json b/package.json index 760f758..745e055 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "format:rs": "cargo +nightly-2024-07-06 fmt --all" }, "devDependencies": { - "@taplo/cli": "^0.5.2", + "@taplo/cli": "^0.7.0", "husky": "^8.0.3", "lint-staged": "^14.0.0", "npm-run-all": "^4.1.5", @@ -41,45 +41,46 @@ ] }, "resolutions": { - "array-buffer-byte-length": "npm:@nolyfill/array-buffer-byte-length@latest", - "arraybuffer.prototype.slice": "npm:@nolyfill/arraybuffer.prototype.slice@latest", - "available-typed-arrays": "npm:@nolyfill/available-typed-arrays@latest", - "define-properties": "npm:@nolyfill/define-properties@latest", - "es-set-tostringtag": "npm:@nolyfill/es-set-tostringtag@latest", - "function-bind": "npm:@nolyfill/function-bind@latest", - "function.prototype.name": "npm:@nolyfill/function.prototype.name@latest", - "get-symbol-description": "npm:@nolyfill/get-symbol-description@latest", - "globalthis": "npm:@nolyfill/globalthis@latest", - "gopd": "npm:@nolyfill/gopd@latest", - "has": "npm:@nolyfill/has@latest", - "has-property-descriptors": "npm:@nolyfill/has-property-descriptors@latest", - "has-proto": "npm:@nolyfill/has-proto@latest", - "has-symbols": "npm:@nolyfill/has-symbols@latest", - "has-tostringtag": "npm:@nolyfill/has-tostringtag@latest", - "internal-slot": "npm:@nolyfill/internal-slot@latest", - "is-array-buffer": "npm:@nolyfill/is-array-buffer@latest", - "is-date-object": "npm:@nolyfill/is-date-object@latest", - "is-regex": "npm:@nolyfill/is-regex@latest", - "is-shared-array-buffer": "npm:@nolyfill/is-shared-array-buffer@latest", - "is-string": "npm:@nolyfill/is-string@latest", - "is-symbol": "npm:@nolyfill/is-symbol@latest", - "is-weakref": "npm:@nolyfill/is-weakref@latest", - "object-keys": "npm:@nolyfill/object-keys@latest", - "object.assign": "npm:@nolyfill/object.assign@latest", - "regexp.prototype.flags": "npm:@nolyfill/regexp.prototype.flags@latest", - "safe-array-concat": "npm:@nolyfill/safe-array-concat@latest", - "safe-regex-test": "npm:@nolyfill/safe-regex-test@latest", - "side-channel": "npm:@nolyfill/side-channel@latest", - "string.prototype.padend": "npm:@nolyfill/string.prototype.padend@latest", - "string.prototype.trim": "npm:@nolyfill/string.prototype.trim@latest", - "string.prototype.trimend": "npm:@nolyfill/string.prototype.trimend@latest", - "string.prototype.trimstart": "npm:@nolyfill/string.prototype.trimstart@latest", - "typed-array-buffer": "npm:@nolyfill/typed-array-buffer@latest", - "typed-array-byte-length": "npm:@nolyfill/typed-array-byte-length@latest", - "typed-array-byte-offset": "npm:@nolyfill/typed-array-byte-offset@latest", - "typed-array-length": "npm:@nolyfill/typed-array-length@latest", - "unbox-primitive": "npm:@nolyfill/unbox-primitive@latest", - "which-boxed-primitive": "npm:@nolyfill/which-boxed-primitive@latest", - "which-typed-array": "npm:@nolyfill/which-typed-array@latest" + "array-buffer-byte-length": "npm:@nolyfill/array-buffer-byte-length@^1", + "arraybuffer.prototype.slice": "npm:@nolyfill/arraybuffer.prototype.slice@^1", + "available-typed-arrays": "npm:@nolyfill/available-typed-arrays@^1", + "define-properties": "npm:@nolyfill/define-properties@^1", + "es-set-tostringtag": "npm:@nolyfill/es-set-tostringtag@^1", + "function-bind": "npm:@nolyfill/function-bind@^1", + "function.prototype.name": "npm:@nolyfill/function.prototype.name@^1", + "get-symbol-description": "npm:@nolyfill/get-symbol-description@^1", + "globalthis": "npm:@nolyfill/globalthis@^1", + "gopd": "npm:@nolyfill/gopd@^1", + "has": "npm:@nolyfill/has@^1", + "has-property-descriptors": "npm:@nolyfill/has-property-descriptors@^1", + "has-proto": "npm:@nolyfill/has-proto@^1", + "has-symbols": "npm:@nolyfill/has-symbols@^1", + "has-tostringtag": "npm:@nolyfill/has-tostringtag@^1", + "internal-slot": "npm:@nolyfill/internal-slot@^1", + "is-array-buffer": "npm:@nolyfill/is-array-buffer@^1", + "is-date-object": "npm:@nolyfill/is-date-object@^1", + "is-regex": "npm:@nolyfill/is-regex@^1", + "is-shared-array-buffer": "npm:@nolyfill/is-shared-array-buffer@^1", + "is-string": "npm:@nolyfill/is-string@^1", + "is-symbol": "npm:@nolyfill/is-symbol@^1", + "is-weakref": "npm:@nolyfill/is-weakref@^1", + "object-keys": "npm:@nolyfill/object-keys@^1", + "object.assign": "npm:@nolyfill/object.assign@^1", + "regexp.prototype.flags": "npm:@nolyfill/regexp.prototype.flags@^1", + "safe-array-concat": "npm:@nolyfill/safe-array-concat@^1", + "safe-regex-test": "npm:@nolyfill/safe-regex-test@^1", + "side-channel": "npm:@nolyfill/side-channel@^1", + "string.prototype.padend": "npm:@nolyfill/string.prototype.padend@^1", + "string.prototype.trim": "npm:@nolyfill/string.prototype.trim@^1", + "string.prototype.trimend": "npm:@nolyfill/string.prototype.trimend@^1", + "string.prototype.trimstart": "npm:@nolyfill/string.prototype.trimstart@^1", + "typed-array-buffer": "npm:@nolyfill/typed-array-buffer@^1", + "typed-array-byte-length": "npm:@nolyfill/typed-array-byte-length@^1", + "typed-array-byte-offset": "npm:@nolyfill/typed-array-byte-offset@^1", + "typed-array-length": "npm:@nolyfill/typed-array-length@^1", + "unbox-primitive": "npm:@nolyfill/unbox-primitive@^1", + "which-boxed-primitive": "npm:@nolyfill/which-boxed-primitive@^1", + "which-typed-array": "npm:@nolyfill/which-typed-array@^1", + "is-core-module": "npm:@nolyfill/is-core-module@^1" } } diff --git a/y-octo-node/package.json b/y-octo-node/package.json index 3b299d0..5475922 100644 --- a/y-octo-node/package.json +++ b/y-octo-node/package.json @@ -21,10 +21,10 @@ "license": "MIT", "devDependencies": { "@napi-rs/cli": "^2.16.2", - "@types/node": "^18.17.5", + "@types/node": "^20.16.4", "@types/prompts": "^2.4.4", "ava": "^6.1.3", - "c8": "^8.0.1", + "c8": "^10.1.2", "prompts": "^2.4.2", "tsx": "^4.16.2", "typescript": "^5.1.6", diff --git a/y-octo-node/src/array.rs b/y-octo-node/src/array.rs index 3c087d5..3d2fd87 100644 --- a/y-octo-node/src/array.rs +++ b/y-octo-node/src/array.rs @@ -145,14 +145,16 @@ impl YArray { } #[napi( - ts_args_type = "value: YArray | YMap | YText | boolean | number | string | Record | null | undefined" + ts_args_type = "value: YArray | YMap | YText | boolean | number | string | Record | null | \ + undefined" )] pub fn push(&mut self, value: MixedRefYType) -> Result<()> { self.insert(self.length(), value) } #[napi( - ts_args_type = "value: YArray | YMap | YText | boolean | number | string | Record | null | undefined" + ts_args_type = "value: YArray | YMap | YText | boolean | number | string | Record | null | \ + undefined" )] pub fn unshift(&mut self, value: MixedRefYType) -> Result<()> { self.insert(0, value) diff --git a/y-octo-node/src/lib.rs b/y-octo-node/src/lib.rs index 56ea290..c5f3016 100644 --- a/y-octo-node/src/lib.rs +++ b/y-octo-node/src/lib.rs @@ -17,7 +17,6 @@ pub use function::*; pub use map::*; pub use text::*; pub use types::*; - use utils::{ get_any_from_js_object, get_any_from_js_unknown, get_js_unknown_from_any, get_js_unknown_from_value, MixedRefYType, MixedYType, diff --git a/y-octo-node/src/map.rs b/y-octo-node/src/map.rs index 2069cb2..d7bdcad 100644 --- a/y-octo-node/src/map.rs +++ b/y-octo-node/src/map.rs @@ -51,7 +51,8 @@ impl YMap { } #[napi( - ts_generic_types = "T = YArray | YMap | YText | boolean | number | string | Record | null | undefined", + ts_generic_types = "T = YArray | YMap | YText | boolean | number | string | Record | null | \ + undefined", ts_args_type = "key: string, value: T", ts_return_type = "T" )] diff --git a/y-octo-node/tests/yjs/testHelper.ts b/y-octo-node/tests/yjs/testHelper.ts index 4a6b9e8..f89cfe1 100644 --- a/y-octo-node/tests/yjs/testHelper.ts +++ b/y-octo-node/tests/yjs/testHelper.ts @@ -336,9 +336,7 @@ export const compare = (users: TestYOctoInstance[]) => { // Test Map iterator const ymapkeys: any[] = Array.from(users[0].getOrCreateMap("map").keys()); assert(ymapkeys.length === Object.keys(userMapValues[0]).length); - ymapkeys.forEach((key) => - assert(object.hasProperty(userMapValues[0], key)), - ); + ymapkeys.forEach((key) => assert(object.hasProperty(userMapValues[0], key))); const mapRes: Record = {}; for (const [k, v] of users[0].getOrCreateMap("map").entries()) { diff --git a/yarn.lock b/yarn.lock index b6ee53e..1a5c320 100644 --- a/yarn.lock +++ b/yarn.lock @@ -273,10 +273,10 @@ __metadata: languageName: node linkType: hard -"@nolyfill/shared@npm:1.0.20": - version: 1.0.20 - resolution: "@nolyfill/shared@npm:1.0.20" - checksum: cb2511c301e0e5dc15926a59a396352d0dd7f3376979c9526538ee25e223bc1d4524ecca4d245c1cd0c377d47ddbcad7f606d151f382b00f47844ef5f61872e5 +"@nolyfill/shared@npm:1.0.28": + version: 1.0.28 + resolution: "@nolyfill/shared@npm:1.0.28" + checksum: 395261f73688e2a58f78c34238a13176feb2b79b0a331d9bcf6bfdb9d8473115934468e73eefe89614a0e5cb0b896d900c0e738de452fbb4d3648c944db02428 languageName: node linkType: hard @@ -326,12 +326,12 @@ __metadata: languageName: node linkType: hard -"@taplo/cli@npm:^0.5.2": - version: 0.5.2 - resolution: "@taplo/cli@npm:0.5.2" +"@taplo/cli@npm:^0.7.0": + version: 0.7.0 + resolution: "@taplo/cli@npm:0.7.0" bin: taplo: dist/cli.js - checksum: c2e0e584172bfee1cca6624bdb4470259179e232472fc7f4bbbd2e0127233039b9ace21a8d6b8d5081b157d9f046dc942ab27a634e23924b8c8a6096f1d04e27 + checksum: ec8e64e08c664961104e2d4abb8babd2bc5613b8c76444f02d2aec155e456af4c13e3e8dac86af8ab279cc5818d9cc094de71bd04c264d002f2a086fa6477b5f languageName: node linkType: hard @@ -349,10 +349,12 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^18.17.5": - version: 18.17.6 - resolution: "@types/node@npm:18.17.6" - checksum: 70bc92adde47d569f25c5ed40b55040cdf189518d6149e0c3041c6e60b1098cad9c48a856f0b7868ebd74d4098a0ca508b0ec4373dd96216eb8a387ee898e14c +"@types/node@npm:^20.16.4": + version: 20.16.4 + resolution: "@types/node@npm:20.16.4" + dependencies: + undici-types: ~6.19.2 + checksum: 533315175e2d46a38cb220e3fb41ecfb33625b95cae1120e001eab0aa23330ff02a4dc44e15fca39fd420a802da3b20b3e5d30a21daf5e4853ea48c16ec76a8e languageName: node linkType: hard @@ -392,7 +394,7 @@ __metadata: version: 0.0.0-use.local resolution: "@y-octo/cli@workspace:." dependencies: - "@taplo/cli": ^0.5.2 + "@taplo/cli": ^0.7.0 husky: ^8.0.3 lint-staged: ^14.0.0 npm-run-all: ^4.1.5 @@ -405,10 +407,10 @@ __metadata: resolution: "@y-octo/node@workspace:y-octo-node" dependencies: "@napi-rs/cli": ^2.16.2 - "@types/node": ^18.17.5 + "@types/node": ^20.16.4 "@types/prompts": ^2.4.4 ava: ^6.1.3 - c8: ^8.0.1 + c8: ^10.1.2 prompts: ^2.4.2 tsx: ^4.16.2 typescript: ^5.1.6 @@ -703,25 +705,29 @@ __metadata: languageName: node linkType: hard -"c8@npm:^8.0.1": - version: 8.0.1 - resolution: "c8@npm:8.0.1" +"c8@npm:^10.1.2": + version: 10.1.2 + resolution: "c8@npm:10.1.2" dependencies: "@bcoe/v8-coverage": ^0.2.3 "@istanbuljs/schema": ^0.1.3 find-up: ^5.0.0 - foreground-child: ^2.0.0 + foreground-child: ^3.1.1 istanbul-lib-coverage: ^3.2.0 istanbul-lib-report: ^3.0.1 istanbul-reports: ^3.1.6 - rimraf: ^3.0.2 - test-exclude: ^6.0.0 + test-exclude: ^7.0.1 v8-to-istanbul: ^9.0.0 yargs: ^17.7.2 yargs-parser: ^21.1.1 + peerDependencies: + monocart-coverage-reports: ^2 + peerDependenciesMeta: + monocart-coverage-reports: + optional: true bin: c8: bin/c8.js - checksum: 2c47531d21cb67b1e533fbb203ddb5a1c4b45d52c004dcf4eb1376ac8df205f2f4a1b2b9611777ca88dadbbcc2bbdad26b8c5f7ca58a02ecd52afa2aebef73fe + checksum: 8a07191df0ff7fa267ba7701967876cfb7e4f8f7fac7de6dfdf7b329b2d95e2ed1aeeacad5c8646d0c3aba44333e3b57414dbc4494263b06ffba46002450629f languageName: node linkType: hard @@ -1359,23 +1365,23 @@ __metadata: languageName: node linkType: hard -"foreground-child@npm:^2.0.0": - version: 2.0.0 - resolution: "foreground-child@npm:2.0.0" +"foreground-child@npm:^3.1.0": + version: 3.2.1 + resolution: "foreground-child@npm:3.2.1" dependencies: cross-spawn: ^7.0.0 - signal-exit: ^3.0.2 - checksum: f77ec9aff621abd6b754cb59e690743e7639328301fbea6ff09df27d2befaf7dd5b77cec51c32323d73a81a7d91caaf9413990d305cbe3d873eec4fe58960956 + signal-exit: ^4.0.1 + checksum: 3e2e844d6003c96d70affe8ae98d7eaaba269a868c14d997620c088340a8775cd5d2d9043e6ceebae1928d8d9a874911c4d664b9a267e8995945df20337aebc0 languageName: node linkType: hard -"foreground-child@npm:^3.1.0": - version: 3.2.1 - resolution: "foreground-child@npm:3.2.1" +"foreground-child@npm:^3.1.1": + version: 3.3.0 + resolution: "foreground-child@npm:3.3.0" dependencies: cross-spawn: ^7.0.0 signal-exit: ^4.0.1 - checksum: 3e2e844d6003c96d70affe8ae98d7eaaba269a868c14d997620c088340a8775cd5d2d9043e6ceebae1928d8d9a874911c4d664b9a267e8995945df20337aebc0 + checksum: 1989698488f725b05b26bc9afc8a08f08ec41807cd7b92ad85d96004ddf8243fd3e79486b8348c64a3011ae5cc2c9f0936af989e1f28339805d8bc178a75b451 languageName: node linkType: hard @@ -1495,7 +1501,23 @@ __metadata: languageName: node linkType: hard -"glob@npm:^7.1.3, glob@npm:^7.1.4": +"glob@npm:^10.4.1": + version: 10.4.5 + resolution: "glob@npm:10.4.5" + dependencies: + foreground-child: ^3.1.0 + jackspeak: ^3.1.2 + minimatch: ^9.0.4 + minipass: ^7.1.2 + package-json-from-dist: ^1.0.0 + path-scurry: ^1.11.1 + bin: + glob: dist/esm/bin.mjs + checksum: 0bc725de5e4862f9f387fd0f2b274baf16850dcd2714502ccf471ee401803997983e2c05590cb65f9675a3c6f2a58e7a53f9e365704108c6ad3cbf1d60934c4a + languageName: node + linkType: hard + +"glob@npm:^7.1.3": version: 7.2.3 resolution: "glob@npm:7.2.3" dependencies: @@ -1551,15 +1573,6 @@ __metadata: languageName: node linkType: hard -"has@npm:@nolyfill/has@latest": - version: 1.0.20 - resolution: "@nolyfill/has@npm:1.0.20" - dependencies: - "@nolyfill/shared": 1.0.20 - checksum: b92b5b617a90b210cb84ecffae2dc407e087b83fcbe8658f7b422b21e93ba874d807c395e2d18a2efe93b13f9430fdebd0f2aa27bf641be78a66f79a6f536578 - languageName: node - linkType: hard - "hosted-git-info@npm:^2.1.4": version: 2.8.9 resolution: "hosted-git-info@npm:2.8.9" @@ -1712,12 +1725,10 @@ __metadata: languageName: node linkType: hard -"is-core-module@npm:^2.13.0": - version: 2.13.0 - resolution: "is-core-module@npm:2.13.0" - dependencies: - has: ^1.0.3 - checksum: 053ab101fb390bfeb2333360fd131387bed54e476b26860dc7f5a700bbf34a0ec4454f7c8c4d43e8a0030957e4b3db6e16d35e1890ea6fb654c833095e040355 +"is-core-module@npm:@nolyfill/is-core-module@^1": + version: 1.0.39 + resolution: "@nolyfill/is-core-module@npm:1.0.39" + checksum: 0d6e098b871eca71d875651288e1f0fa770a63478b0b50479c99dc760c64175a56b5b04f58d5581bbcc6b552b8191ab415eada093d8df9597ab3423c8cac1815 languageName: node linkType: hard @@ -3077,12 +3088,12 @@ __metadata: languageName: node linkType: hard -"string.prototype.padend@npm:@nolyfill/string.prototype.padend@latest": - version: 1.0.20 - resolution: "@nolyfill/string.prototype.padend@npm:1.0.20" +"string.prototype.padend@npm:@nolyfill/string.prototype.padend@^1": + version: 1.0.28 + resolution: "@nolyfill/string.prototype.padend@npm:1.0.28" dependencies: - "@nolyfill/shared": 1.0.20 - checksum: 4f7853e46d8ec4d295f741d2139eeb8bddc7d64a9016abe89d9e74cd1c96252a7948831afba16afed64ac67d3eb5b3affc2e1ebcb6d871cdf4967e9eba7f2e50 + "@nolyfill/shared": 1.0.28 + checksum: 47a51619785ca1b8a852b742a6c88f807b2f89c91affa51ab3b946632c08e82cd8f91c0f61de282670227a03efd3b4258f7ff97a910b2c4e627e020d11380e96 languageName: node linkType: hard @@ -3185,14 +3196,14 @@ __metadata: languageName: node linkType: hard -"test-exclude@npm:^6.0.0": - version: 6.0.0 - resolution: "test-exclude@npm:6.0.0" +"test-exclude@npm:^7.0.1": + version: 7.0.1 + resolution: "test-exclude@npm:7.0.1" dependencies: "@istanbuljs/schema": ^0.1.2 - glob: ^7.1.4 - minimatch: ^3.0.4 - checksum: 3b34a3d77165a2cb82b34014b3aba93b1c4637a5011807557dc2f3da826c59975a5ccad765721c4648b39817e3472789f9b0fa98fc854c5c1c7a1e632aacdc28 + glob: ^10.4.1 + minimatch: ^9.0.4 + checksum: e5a49a054bf2da74467dd8149b202166e36275c0dc2c9585f7d34de99c6d055d2287ac8d2a8e4c27c59b893acbc671af3fa869e8069a58ad117250e9c01c726b languageName: node linkType: hard @@ -3269,6 +3280,13 @@ __metadata: languageName: node linkType: hard +"undici-types@npm:~6.19.2": + version: 6.19.8 + resolution: "undici-types@npm:6.19.8" + checksum: de51f1b447d22571cf155dfe14ff6d12c5bdaec237c765085b439c38ca8518fc360e88c70f99469162bf2e14188a7b0bcb06e1ed2dc031042b984b0bb9544017 + languageName: node + linkType: hard + "unicorn-magic@npm:^0.1.0": version: 0.1.0 resolution: "unicorn-magic@npm:0.1.0" From 8db17b12e16c7cf1e977284124ba30a9b56c06c5 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Mon, 19 Aug 2024 17:57:10 +0800 Subject: [PATCH 29/75] feat: add un-integrated yarray api support --- y-octo-node/src/array.rs | 244 ++++++++++++++++++++++++++++++++------- y-octo-node/src/doc.rs | 14 ++- y-octo-node/src/map.rs | 31 +++-- y-octo-node/src/utils.rs | 12 +- 4 files changed, 240 insertions(+), 61 deletions(-) diff --git a/y-octo-node/src/array.rs b/y-octo-node/src/array.rs index 3d2fd87..41627ca 100644 --- a/y-octo-node/src/array.rs +++ b/y-octo-node/src/array.rs @@ -1,48 +1,200 @@ +use std::{ + ops::DerefMut, + sync::{Arc, RwLock}, +}; + use napi::{ bindgen_prelude::{Array as JsArray, FromNapiValue}, iterator::Generator, Env, JsFunction, JsUnknown, ValueType, }; -use y_octo::{Any, Array, Value}; +use y_octo::{Any, Array, Doc, JwstCodecResult, Value}; use super::*; +fn cannot_insert_nested_ytype() -> anyhow::Error { + anyhow::Error::msg("Cannot insert nested y-type before current raw-type integrated") +} + +#[derive(Clone)] +pub(crate) enum ManagedArray { + YArray { array: Array, doc: Doc }, + JsArray(Vec), +} + +impl TryFrom for ManagedArray { + type Error = anyhow::Error; + + fn try_from(array: JsArray) -> Result { + match array + .coerce_to_object() + .and_then(|o| o.get_array_length().map(|l| (o, l))) + { + Ok((object, length)) => { + let mut array = vec![]; + for i in 0..length { + if let Ok(unknown) = object.get_element::(i) { + match get_any_from_js_unknown(unknown) { + Ok(any) => array.push(any), + Err(e) => { + return Err( + anyhow::Error::new(e).context(format!("Failed to coerce value to any: {}", i)) + ) + } + } + } + } + Ok(Self::JsArray(array)) + } + Err(e) => Err(anyhow::Error::new(e).context("Failed to coerce value to array")), + } + } +} + +impl ManagedArray { + fn integrate_to_ytype(&mut self, doc: &Doc) -> JwstCodecResult { + match self { + Self::YArray { array, .. } => Ok(array.clone()), + Self::JsArray(array) => { + let mut yarray = doc.create_array()?; + for value in array { + yarray.push(value.clone())?; + } + *self = Self::YArray { + array: yarray.clone(), + doc: doc.clone(), + }; + Ok(yarray) + } + } + } + + fn doc(&self) -> Option<&Doc> { + match self { + Self::YArray { doc, .. } => Some(doc), + Self::JsArray(_) => None, + } + } + + fn len(&self) -> u64 { + match self { + Self::YArray { array, .. } => array.len(), + Self::JsArray(array) => array.len() as u64, + } + } + + fn is_empty(&self) -> bool { + match self { + Self::YArray { array, .. } => array.is_empty(), + Self::JsArray(array) => array.is_empty(), + } + } + + fn get(&self, env: Env, index: i64) -> Result { + match self { + Self::YArray { array, doc } => { + if let Some(value) = array.get(index as u64) { + match value { + Value::Any(any) => get_js_unknown_from_any(env, any).map(MixedYType::D), + Value::Array(array) => Ok(MixedYType::A(YArray::inner_new(array, doc))), + Value::Map(map) => Ok(MixedYType::B(YMap::inner_new(map, doc))), + Value::Text(text) => Ok(MixedYType::C(YText::inner_new(text))), + _ => env.get_null().map(|v| v.into_unknown()).map(MixedYType::D), + } + .map_err(anyhow::Error::from) + } else { + Ok(MixedYType::D(env.get_null()?.into_unknown())) + } + } + Self::JsArray(array) => array + .get(index as usize) + .ok_or_else(|| anyhow::Error::msg("Failed to get value from array")) + .and_then(|any| { + get_js_unknown_from_any(env, any.clone()) + .map(MixedYType::D) + .map_err(anyhow::Error::from) + }), + } + } + + fn iter(&self) -> Box + '_> { + match self { + Self::YArray { array, .. } => Box::new(array.iter().map(|v| v.into())), + Self::JsArray(array) => Box::new(array.iter().map(|v| Value::Any(v.clone()))), + } + } + + fn insert>(&mut self, index: u64, value: V) -> Result<(), anyhow::Error> { + match self { + Self::YArray { array, .. } => array.insert(index, value).map_err(anyhow::Error::from), + Self::JsArray(array) => match value.into() { + Value::Any(any) => Ok(array.insert(index as usize, any)), + Value::Array(_) | Value::Map(_) | Value::Text(_) => Err(cannot_insert_nested_ytype()), + _ => Err(anyhow::Error::msg("Unsupported value type")), + }, + } + } + + fn remove(&mut self, index: u64, len: u64) -> Result<(), anyhow::Error> { + match self { + Self::YArray { array, .. } => array.remove(index, len).map_err(anyhow::Error::from), + Self::JsArray(array) => { + if index >= array.len() as u64 { + return Err(anyhow::Error::msg("Index out of bounds")); + } else if index + len > array.len() as u64 { + return Err(anyhow::Error::msg("Length out of bounds")); + } else if len == 0 { + return Ok(()); + } + + array.drain((index as usize)..(index + len) as usize); + Ok(()) + } + } + } +} + #[napi] #[derive(Clone)] pub struct YArray { - pub(crate) array: Array, + pub(crate) array: Arc>, } #[napi] impl YArray { - pub(crate) fn inner_new(array: Array) -> Self { - Self { array } + pub(crate) fn inner_new(array: Array, doc: &Doc) -> Self { + Self { + array: Arc::new(RwLock::new(ManagedArray::YArray { + array, + doc: doc.clone(), + })), + } + } + + #[napi] + pub fn from(array: JsArray) -> Result { + Ok(Self { + array: Arc::new(RwLock::new(array.try_into()?)), + }) + } + + pub(crate) fn integrate_to_ytype(&self, doc: &Doc) -> JwstCodecResult { + self.array.write().unwrap().integrate_to_ytype(doc) } #[napi(getter)] pub fn length(&self) -> i64 { - self.array.len() as i64 + self.array.read().unwrap().len() as i64 } #[napi(getter)] pub fn is_empty(&self) -> bool { - self.array.is_empty() + self.array.read().unwrap().is_empty() } #[napi(ts_generic_types = "T = unknown", ts_return_type = "T")] pub fn get(&self, env: Env, index: i64) -> Result { - if let Some(value) = self.array.get(index as u64) { - match value { - Value::Any(any) => get_js_unknown_from_any(env, any).map(MixedYType::D), - Value::Array(array) => Ok(MixedYType::A(YArray::inner_new(array))), - Value::Map(map) => Ok(MixedYType::B(YMap::inner_new(map))), - Value::Text(text) => Ok(MixedYType::C(YText::inner_new(text))), - _ => env.get_null().map(|v| v.into_unknown()).map(MixedYType::D), - } - .map_err(anyhow::Error::from) - } else { - Ok(MixedYType::D(env.get_null()?.into_unknown())) - } + self.array.read().unwrap().get(env, index) } #[napi(ts_generic_types = "T = unknown", ts_return_type = "Array")] @@ -62,8 +214,10 @@ impl YArray { } }) .unwrap_or(self.length() - start) as usize; - for value in self.array.iter().skip(start as usize).take(end) { - js_array.insert(get_js_unknown_from_value(env, value)?)?; + let array = self.array.read().unwrap(); + let doc = array.doc(); + for value in array.iter().skip(start as usize).take(end) { + js_array.insert(get_js_unknown_from_value(env, doc, value)?)?; } Ok(js_array) } @@ -71,8 +225,10 @@ impl YArray { #[napi(ts_generic_types = "T = unknown", ts_return_type = "Array")] pub fn map(&self, env: Env, callback: JsFunction) -> Result { let mut js_array = env.create_array(0)?; - for value in self.array.iter() { - let js_value = get_js_unknown_from_value(env, value)?; + let array = self.array.read().unwrap(); + let doc = array.doc(); + for value in array.iter() { + let js_value = get_js_unknown_from_value(env, doc, value)?; let result = callback.call(None, &[js_value.into_unknown()])?; js_array.insert(result)?; } @@ -84,30 +240,31 @@ impl YArray { | null | undefined" )] pub fn insert(&mut self, index: i64, value: MixedRefYType) -> Result<()> { + let mut this = self.array.write().unwrap(); match value { - MixedRefYType::A(array) => self - .array - .insert(index as u64, array.array.clone()) - .map_err(anyhow::Error::from), - MixedRefYType::B(map) => self - .array - .insert(index as u64, map.map.clone()) - .map_err(anyhow::Error::from), - MixedRefYType::C(text) => self - .array + MixedRefYType::A(array) => { + if let ManagedArray::YArray { array: yarray, doc } = this.deref_mut() { + let array = array.integrate_to_ytype(doc).map_err(anyhow::Error::from)?; + yarray.insert(index as u64, array).map_err(anyhow::Error::from) + } else { + Err(cannot_insert_nested_ytype()) + } + } + MixedRefYType::B(map) => this.insert(index as u64, map.map.clone()).map_err(anyhow::Error::from), + MixedRefYType::C(text) => this .insert(index as u64, text.text.clone()) .map_err(anyhow::Error::from), MixedRefYType::D(unknown) => match unknown.get_type() { Ok(value_type) => match value_type { ValueType::Undefined | ValueType::Null => { - self.array.insert(index as u64, Any::Null).map_err(anyhow::Error::from) + this.insert(index as u64, Any::Null).map_err(anyhow::Error::from) } ValueType::Boolean => match unknown.coerce_to_bool().and_then(|v| v.get_value()) { - Ok(boolean) => self.array.insert(index as u64, boolean).map_err(anyhow::Error::from), + Ok(boolean) => this.insert(index as u64, boolean).map_err(anyhow::Error::from), Err(e) => Err(anyhow::Error::new(e).context("Failed to coerce value to boolean")), }, ValueType::Number => match unknown.coerce_to_number().and_then(|v| v.get_double()) { - Ok(number) => self.array.insert(index as u64, number).map_err(anyhow::Error::from), + Ok(number) => this.insert(index as u64, number).map_err(anyhow::Error::from), Err(e) => Err(anyhow::Error::new(e).context("Failed to coerce value to number")), }, ValueType::String => { @@ -116,7 +273,7 @@ impl YArray { .and_then(|v| v.into_utf8()) .and_then(|s| s.as_str().map(|s| s.to_string())) { - Ok(string) => self.array.insert(index as u64, string).map_err(anyhow::Error::from), + Ok(string) => this.insert(index as u64, string).map_err(anyhow::Error::from), Err(e) => Err(anyhow::Error::new(e).context("Failed to coerce value to string")), } } @@ -125,6 +282,7 @@ impl YArray { .and_then(|o| o.get_array_length().map(|l| (o, l))) { Ok((object, length)) => { + drop(this); for i in 0..length { if let Ok(unknown) = object.get_element::(i) { self.insert(index + i as i64, MixedRefYType::from_unknown(unknown)?)?; @@ -163,6 +321,8 @@ impl YArray { #[napi] pub fn delete(&mut self, index: i64, len: Option) -> Result<()> { self.array + .write() + .unwrap() .remove(index as u64, len.unwrap_or(1) as u64) .map_err(anyhow::Error::from) } @@ -179,8 +339,10 @@ impl YArray { #[napi] pub fn to_array(&self, env: Env) -> Result { let mut js_array = env.create_array(0)?; - for value in self.array.iter() { - js_array.insert(get_js_unknown_from_value(env, value)?)?; + let array = self.array.read().unwrap(); + let doc = array.doc(); + for value in array.iter() { + js_array.insert(get_js_unknown_from_value(env, doc, value)?)?; } Ok(js_array) } @@ -188,8 +350,10 @@ impl YArray { #[napi(js_name = "toJSON")] pub fn to_json(&self, env: Env) -> Result { let mut js_array = env.create_array(0)?; - for value in self.array.iter() { - js_array.insert(get_js_unknown_from_value(env, value)?)?; + let array = self.array.read().unwrap(); + let doc = array.doc(); + for value in array.iter() { + js_array.insert(get_js_unknown_from_value(env, doc, value)?)?; } Ok(js_array) } diff --git a/y-octo-node/src/doc.rs b/y-octo-node/src/doc.rs index c709cf7..35130c7 100644 --- a/y-octo-node/src/doc.rs +++ b/y-octo-node/src/doc.rs @@ -54,7 +54,7 @@ impl YDoc { pub fn get_or_create_array(&self, key: String) -> Result { self.doc .get_or_create_array(key) - .map(YArray::inner_new) + .map(|a| YArray::inner_new(a, &self.doc)) .map_err(anyhow::Error::from) } @@ -70,7 +70,7 @@ impl YDoc { pub fn get_or_create_map(&self, key: String) -> Result { self.doc .get_or_create_map(key) - .map(YMap::inner_new) + .map(|m| YMap::inner_new(m, &self.doc)) .map_err(anyhow::Error::from) } @@ -78,7 +78,7 @@ impl YDoc { pub fn create_array(&self) -> Result { self.doc .create_array() - .map(YArray::inner_new) + .map(|a| YArray::inner_new(a, &self.doc)) .map_err(anyhow::Error::from) } @@ -93,7 +93,7 @@ impl YDoc { #[napi] pub fn create_map(&self, env: Env, entries: Option) -> Result { - let mut ymap = self.doc.create_map().map(YMap::inner_new)?; + let mut ymap = self.doc.create_map().map(|m| YMap::inner_new(m, &self.doc))?; if let Some(entries) = entries { for i in 0..entries.len() { if let Ok(Some(value)) = entries.get::(i) { @@ -150,6 +150,12 @@ impl YDoc { Ok(()) } + #[napi] + pub fn off_update(&mut self) -> Result<()> { + self.doc.unsubscribe_all(); + Ok(()) + } + #[napi] pub fn transact(&mut self, callback: JsFunction) -> Result<()> { callback.call_without_args(None)?; diff --git a/y-octo-node/src/map.rs b/y-octo-node/src/map.rs index d7bdcad..ae27901 100644 --- a/y-octo-node/src/map.rs +++ b/y-octo-node/src/map.rs @@ -1,17 +1,18 @@ use napi::{bindgen_prelude::Array as JsArray, iterator::Generator, Env, JsFunction, JsObject, JsUnknown, ValueType}; -use y_octo::{Any, Map, Value}; +use y_octo::{Any, Doc, Map, Value}; use super::*; #[napi] pub struct YMap { pub(crate) map: Map, + doc: Doc, } #[napi] impl YMap { - pub(crate) fn inner_new(map: Map) -> Self { - Self { map } + pub(crate) fn inner_new(map: Map, doc: &Doc) -> Self { + Self { map, doc: doc.clone() } } #[napi(getter)] @@ -39,8 +40,8 @@ impl YMap { if let Some(value) = self.map.get(&key) { match value { Value::Any(any) => get_js_unknown_from_any(env, any).map(MixedYType::D), - Value::Array(array) => Ok(MixedYType::A(YArray::inner_new(array))), - Value::Map(map) => Ok(MixedYType::B(YMap::inner_new(map))), + Value::Array(array) => Ok(MixedYType::A(YArray::inner_new(array, &self.doc))), + Value::Map(map) => Ok(MixedYType::B(YMap::inner_new(map, &self.doc))), Value::Text(text) => Ok(MixedYType::C(YText::inner_new(text))), _ => env.get_null().map(|v| v.into_unknown()).map(MixedYType::D), } @@ -59,12 +60,13 @@ impl YMap { pub fn set(&mut self, env: Env, key: String, value: MixedRefYType) -> Result { match value { MixedRefYType::A(array) => { - self.map.insert(key, array.array.clone())?; - Ok(MixedYType::A(YArray::inner_new(array.array.clone()))) + let array = array.integrate_to_ytype(&self.doc).map_err(anyhow::Error::from)?; + self.map.insert(key, array.clone())?; + Ok(MixedYType::A(YArray::inner_new(array, &self.doc))) } MixedRefYType::B(map) => { self.map.insert(key, map.map.clone())?; - Ok(MixedYType::B(YMap::inner_new(map.map.clone()))) + Ok(MixedYType::B(YMap::inner_new(map.map.clone(), &self.doc))) } MixedRefYType::C(text) => { self.map.insert(key, text.text.clone())?; @@ -137,7 +139,7 @@ impl YMap { pub fn to_json(&self, env: Env) -> Result { let mut js_object = env.create_object()?; for (key, value) in self.map.iter() { - js_object.set(key, get_js_unknown_from_value(env, value))?; + js_object.set(key, get_js_unknown_from_value(env, Some(&self.doc), value))?; } Ok(js_object) } @@ -147,6 +149,7 @@ impl YMap { YMapEntriesIterator { entries: self.map.iter().map(|(k, v)| (k.to_owned(), v)).collect(), env, + doc: self.doc.clone(), current: 0, } } @@ -164,6 +167,7 @@ impl YMap { YMapValuesIterator { entries: self.map.iter().map(|(_, v)| v).collect(), env, + doc: self.doc.clone(), current: 0, } } @@ -185,6 +189,7 @@ impl YMap { pub struct YMapEntriesIterator { entries: Vec<(String, Value)>, env: Env, + doc: Doc, current: i64, } @@ -205,7 +210,10 @@ impl Generator for YMapEntriesIterator { let mut js_array = self.env.create_array(2).ok()?; js_array.set(0, string).ok()?; js_array - .set(1, get_js_unknown_from_value(self.env, value.clone()).ok()?) + .set( + 1, + get_js_unknown_from_value(self.env, Some(&self.doc), value.clone()).ok()?, + ) .ok()?; Some(js_array) } else { @@ -253,6 +261,7 @@ impl Generator for YMapKeyIterator { pub struct YMapValuesIterator { entries: Vec, env: Env, + doc: Doc, current: i64, } @@ -270,7 +279,7 @@ impl Generator for YMapValuesIterator { return None; } let ret = if let Some(value) = self.entries.get(current) { - get_js_unknown_from_value(self.env, value.clone()).ok() + get_js_unknown_from_value(self.env, Some(&self.doc), value.clone()).ok() } else { None }; diff --git a/y-octo-node/src/utils.rs b/y-octo-node/src/utils.rs index ef1f4fc..4ae9613 100644 --- a/y-octo-node/src/utils.rs +++ b/y-octo-node/src/utils.rs @@ -1,5 +1,5 @@ use napi::{bindgen_prelude::Either4, Env, Error, JsObject, JsUnknown, Result, Status, ValueType}; -use y_octo::{AHashMap, Any, HashMapExt, Value}; +use y_octo::{AHashMap, Any, Doc, HashMapExt, Value}; use super::*; @@ -34,14 +34,14 @@ pub fn get_js_unknown_from_any(env: Env, any: Any) -> Result { } } -pub fn get_js_unknown_from_value(env: Env, value: Value) -> Result { +pub fn get_js_unknown_from_value(env: Env, doc: Option<&Doc>, value: Value) -> Result { match value { Value::Any(any) => get_js_unknown_from_any(env, any), - Value::Array(array) => env - .create_external(YArray::inner_new(array), None) + Value::Array(array) if doc.is_some() => env + .create_external(YArray::inner_new(array, doc.unwrap()), None) .map(|o| o.into_unknown()), - Value::Map(map) => env - .create_external(YMap::inner_new(map), None) + Value::Map(map) if doc.is_some() => env + .create_external(YMap::inner_new(map, doc.unwrap()), None) .map(|o| o.into_unknown()), Value::Text(text) => env .create_external(YText::inner_new(text), None) From fcd1c69e49b4dad98d5edd1e6aeec44e6804403d Mon Sep 17 00:00:00 2001 From: DarkSky Date: Tue, 20 Aug 2024 16:57:40 +0800 Subject: [PATCH 30/75] chore: enable more test case --- y-octo-node/index.d.ts | 2 ++ y-octo-node/tests/yjs/testHelper.ts | 18 +++++++++--------- y-octo-node/tests/yjs/y-array.spec.ts | 4 ++-- y-octo-node/tests/yjs/y-map.spec.ts | 7 +++++-- y-octo/src/doc/document.rs | 4 ++++ 5 files changed, 22 insertions(+), 13 deletions(-) diff --git a/y-octo-node/index.d.ts b/y-octo-node/index.d.ts index 254b3a4..35c1ce7 100644 --- a/y-octo-node/index.d.ts +++ b/y-octo-node/index.d.ts @@ -15,6 +15,7 @@ export declare function applyUpdate(doc: Doc, update: Buffer): void export declare function mergeUpdates(updates: Array): Buffer export declare function isAbstractType(unknown: unknown): boolean export class YArray { + static from(array: JsArray): this get length(): number get isEmpty(): boolean get(index: number): T @@ -57,6 +58,7 @@ export class Doc { encodeStateAsUpdateV1(state?: Buffer | undefined | null): Buffer gc(): void onUpdate(callback: (result: Uint8Array) => void): void + offUpdate(): void transact(callback: (...args: any[]) => any): void } export class YMap { diff --git a/y-octo-node/tests/yjs/testHelper.ts b/y-octo-node/tests/yjs/testHelper.ts index f89cfe1..7d32177 100644 --- a/y-octo-node/tests/yjs/testHelper.ts +++ b/y-octo-node/tests/yjs/testHelper.ts @@ -75,14 +75,14 @@ export class TestYOctoInstance extends Y.Doc { testConnector.allConns.add(this); this.updates = []; // set up observe on local model - // this.onUpdate((update) => { - // // if (origin !== testConnector) { - // // const encoder = encoding.createEncoder(); - // // syncProtocol.writeUpdate(encoder, update); - // // broadcastMessage(this, encoding.toUint8Array(encoder)); - // // } - // this.updates.push(update); - // }); + this.onUpdate((update) => { + // if (origin !== testConnector) { + // const encoder = encoding.createEncoder(); + // syncProtocol.writeUpdate(encoder, update); + // broadcastMessage(this, encoding.toUint8Array(encoder)); + // } + this.updates.push(update); + }); this.connect(); } @@ -90,6 +90,7 @@ export class TestYOctoInstance extends Y.Doc { * Disconnect from TestConnector. */ disconnect() { + this.offUpdate(); this.receiving = new Map(); this.tc.onlineConns.delete(this); } @@ -154,7 +155,6 @@ export class TestConnector { * If this function was unable to flush a message, because there are no more messages to flush, it returns false. true otherwise. */ flushRandomMessage(): boolean { - return false; const gen = this.prng; const conns = Array.from(this.onlineConns).filter( (conn) => conn.receiving.size > 0, diff --git a/y-octo-node/tests/yjs/y-array.spec.ts b/y-octo-node/tests/yjs/y-array.spec.ts index b91ad7b..edb73fa 100644 --- a/y-octo-node/tests/yjs/y-array.spec.ts +++ b/y-octo-node/tests/yjs/y-array.spec.ts @@ -36,7 +36,7 @@ test("testSlice", (t) => { t.deepEqual(arr.slice(0, 2), [0, 1]); }); -test.skip("testArrayFrom", (t) => { +test("testArrayFrom", (t) => { const doc1 = new Y.Doc(); const db1 = doc1.getOrCreateMap("root"); const nestedArray1 = Y.Array.from([0, 1, 2]); @@ -74,7 +74,7 @@ test("testLengthIssue", (t) => { /** * Debugging yjs#314 */ -test.skip("testLengthIssue2", (t) => { +test("testLengthIssue2", (t) => { const doc = new Y.Doc(); const next = doc.createArray(); doc.transact(() => { diff --git a/y-octo-node/tests/yjs/y-map.spec.ts b/y-octo-node/tests/yjs/y-map.spec.ts index 55f46fc..4dda044 100644 --- a/y-octo-node/tests/yjs/y-map.spec.ts +++ b/y-octo-node/tests/yjs/y-map.spec.ts @@ -46,7 +46,7 @@ test.skip("testMapEventError", (t) => { }); test("testMapHavingIterableAsConstructorParamTests", (t) => { - const { users, map0 } = init(gen, { users: 1 }); + const { users, map0, testConnector } = init(gen, { users: 1 }); const m1 = users[0].createMap(Object.entries({ number: 1, string: "hello" })); map0.set("m1", m1); @@ -67,6 +67,7 @@ test("testMapHavingIterableAsConstructorParamTests", (t) => { t.assert(m3.get("string") === "hello"); t.assert(m3.get("object").x === 1); t.assert(m3.get("boolean") === true); + testConnector.disconnectAll(); }); test.skip("testBasicMapTests", (t) => { @@ -240,7 +241,7 @@ test.skip("testGetAndSetOfMapPropertyWithConflict", (t) => { }); test("testSizeAndDeleteOfMapProperty", (t) => { - const { map0 } = init(gen, { users: 1 }); + const { map0, testConnector } = init(gen, { users: 1 }); map0.set("stuff", "c0"); map0.set("otherstuff", "c1"); @@ -255,6 +256,7 @@ test("testSizeAndDeleteOfMapProperty", (t) => { map0.size === 0, `map size after delete is ${map0.size}, expected 0`, ); + testConnector.disconnectAll(); }); test.skip("testGetAndSetAndDeleteOfMapProperty", (t) => { @@ -269,6 +271,7 @@ test.skip("testGetAndSetAndDeleteOfMapProperty", (t) => { t.assert(u.get("stuff") === undefined); } compare(users); + testConnector.disconnectAll() }); test.skip("testSetAndClearOfMapProperties", (t) => { diff --git a/y-octo/src/doc/document.rs b/y-octo/src/doc/document.rs index b94e0d5..7d8b060 100644 --- a/y-octo/src/doc/document.rs +++ b/y-octo/src/doc/document.rs @@ -370,6 +370,10 @@ impl Doc { self.publisher.count() } + pub fn subscriber_count(&self) -> usize { + Arc::::strong_count(&self.publisher) + } + pub fn gc(&self) -> JwstCodecResult<()> { self.store.write().unwrap().optimize() } From 30f1e5bb6ae6d662e2ae3b9d2278fa1c4fd864f2 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Mon, 29 Jul 2024 15:15:33 +0800 Subject: [PATCH 31/75] chore: rename output --- y-octo-node/index.d.ts | 112 +-------- y-octo-node/index.js | 284 +-------------------- y-octo-node/package.json | 6 +- y-octo-node/tests/array.spec.ts | 2 +- y-octo-node/tests/doc.spec.ts | 2 +- y-octo-node/tests/map.spec.ts | 2 +- y-octo-node/tests/text.spec.ts | 2 +- y-octo-node/tests/yjs/testHelper.ts | 2 +- y-octo-node/tests/yjs/y-array.spec.ts | 2 +- y-octo-node/tests/yjs/y-map.spec.ts | 2 +- y-octo-node/tests/yjs/y-text.spec.ts | 2 +- y-octo-node/yocto.d.ts | 112 ++++++++- y-octo-node/yocto.js | 342 +++++++++++++++++++++++++- yarn.lock | 10 +- 14 files changed, 470 insertions(+), 412 deletions(-) diff --git a/y-octo-node/index.d.ts b/y-octo-node/index.d.ts index 35c1ce7..43c09a4 100644 --- a/y-octo-node/index.d.ts +++ b/y-octo-node/index.d.ts @@ -1,108 +1,8 @@ -/* tslint:disable */ -/* eslint-disable */ +import * as Y from "./yocto"; -/* auto-generated by NAPI-RS */ +export class Doc extends Y.Doc {} +export class Array extends Y.YArray {} +export class Map extends Y.YMap {} +export class Text extends Y.YText {} -export declare function encodeStateAsUpdate(doc: Doc, state?: Buffer | undefined | null): Buffer -export declare function encodeStateVector(doc: Doc): Buffer -export declare function compareStructStores(store: YStore, other: YStore): boolean -export declare function compareIds(a?: YId | undefined | null, b?: YId | undefined | null): boolean -export declare function createDeleteSetFromStructStore(store: YStore): YDeleteSet -export declare function equalDeleteSets(a: YDeleteSet, b: YDeleteSet): boolean -export declare function snapshot(doc: Doc): YSnapshot -export declare function encodeSnapshot(snapshot: YSnapshot): Buffer -export declare function applyUpdate(doc: Doc, update: Buffer): void -export declare function mergeUpdates(updates: Array): Buffer -export declare function isAbstractType(unknown: unknown): boolean -export class YArray { - static from(array: JsArray): this - get length(): number - get isEmpty(): boolean - get(index: number): T - slice(start: number, end?: number | undefined | null): Array - map(callback: (...args: any[]) => any): Array - insert(index: number, value: YArray | YMap | YText | boolean | number | string | Record | null | undefined): void - push(value: YArray | YMap | YText | boolean | number | string | Record | null | undefined): void - unshift(value: YArray | YMap | YText | boolean | number | string | Record | null | undefined): void - delete(index: number, len?: number | undefined | null): void - iter(): YArrayIterator - toArray(): JsArray - toJSON(): JsArray - observe(callback: (...args: any[]) => any): void - observeDeep(callback: (...args: any[]) => any): void -} -export class YArrayIterator { - [Symbol.iterator](): Iterator -} -export type YAwareness = Awareness -export class Awareness { - constructor(clientId?: number | undefined | null) - get clientId(): number - get states>(): Record -} -export type YDoc = Doc -export class Doc { - constructor(clientId?: number | undefined | null) - get clientId(): number - set clientId(clientId: number) - get guid(): string - get store(): YStore - get keys(): Array - getOrCreateArray(key: string): YArray - getOrCreateText(key: string): YText - getOrCreateMap(key: string): YMap - createArray(): YArray - createText(text?: string | undefined | null): YText - createMap(entries?: JsArray | undefined | null): YMap - applyUpdate(update: Buffer): void - encodeStateAsUpdateV1(state?: Buffer | undefined | null): Buffer - gc(): void - onUpdate(callback: (result: Uint8Array) => void): void - offUpdate(): void - transact(callback: (...args: any[]) => any): void -} -export class YMap { - get length(): number - get size(): number - get isEmpty(): boolean - get itemId(): YId | null - get(key: string): T - set | null | undefined>(key: string, value: T): T - delete(key: string): void - clear(): void - toJson(): object - entries(): YMapEntriesIterator - keys(): YMapKeyIterator - values(): YMapValuesIterator - observe(callback: (...args: any[]) => any): void - observeDeep(callback: (...args: any[]) => any): void -} -export class YMapEntriesIterator { - [Symbol.iterator](): Iterator -} -export class YMapKeyIterator { - [Symbol.iterator](): Iterator -} -export class YMapValuesIterator { - [Symbol.iterator](): Iterator -} -export class YText { - constructor() - get len(): number - get isEmpty(): boolean - insert(index: number, str: string): void - delete(index: number, len: number): void - get length(): number - applyDelta(delta: JsArray): void - toDelta(): JsArray - toString(): string - observe(callback: (...args: any[]) => any): void - observeDeep(callback: (...args: any[]) => any): void -} -export type YId = Id -export class Id { } -export type YStore = Store -export class Store { } -export type YDeleteSet = DeleteSet -export class DeleteSet { } -export class YSnapshot { } +export * from "./yocto"; diff --git a/y-octo-node/index.js b/y-octo-node/index.js index 5716a51..d61a952 100644 --- a/y-octo-node/index.js +++ b/y-octo-node/index.js @@ -1,280 +1,8 @@ -/* tslint:disable */ -/* eslint-disable */ -/* prettier-ignore */ +import * as Y from "./yocto"; -/* auto-generated by NAPI-RS */ +export const Doc = Y.Doc; +export const Array = Y.YArray; +export const Map = Y.YMap; +export const Text = Y.YText; -const { existsSync, readFileSync } = require('fs') -const { join } = require('path') - -const { platform, arch } = process - -let nativeBinding = null -let localFileExisted = false -let loadError = null - -function isMusl() { - // For Node 10 - if (!process.report || typeof process.report.getReport !== 'function') { - try { - const lddPath = require('child_process').execSync('which ldd').toString().trim() - return readFileSync(lddPath, 'utf8').includes('musl') - } catch (e) { - return true - } - } else { - const { glibcVersionRuntime } = process.report.getReport().header - return !glibcVersionRuntime - } -} - -switch (platform) { - case 'android': - switch (arch) { - case 'arm64': - localFileExisted = existsSync(join(__dirname, 'y-octo.android-arm64.node')) - try { - if (localFileExisted) { - nativeBinding = require('./y-octo.android-arm64.node') - } else { - nativeBinding = require('@y-octo/node-android-arm64') - } - } catch (e) { - loadError = e - } - break - case 'arm': - localFileExisted = existsSync(join(__dirname, 'y-octo.android-arm-eabi.node')) - try { - if (localFileExisted) { - nativeBinding = require('./y-octo.android-arm-eabi.node') - } else { - nativeBinding = require('@y-octo/node-android-arm-eabi') - } - } catch (e) { - loadError = e - } - break - default: - throw new Error(`Unsupported architecture on Android ${arch}`) - } - break - case 'win32': - switch (arch) { - case 'x64': - localFileExisted = existsSync( - join(__dirname, 'y-octo.win32-x64-msvc.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./y-octo.win32-x64-msvc.node') - } else { - nativeBinding = require('@y-octo/node-win32-x64-msvc') - } - } catch (e) { - loadError = e - } - break - case 'ia32': - localFileExisted = existsSync( - join(__dirname, 'y-octo.win32-ia32-msvc.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./y-octo.win32-ia32-msvc.node') - } else { - nativeBinding = require('@y-octo/node-win32-ia32-msvc') - } - } catch (e) { - loadError = e - } - break - case 'arm64': - localFileExisted = existsSync( - join(__dirname, 'y-octo.win32-arm64-msvc.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./y-octo.win32-arm64-msvc.node') - } else { - nativeBinding = require('@y-octo/node-win32-arm64-msvc') - } - } catch (e) { - loadError = e - } - break - default: - throw new Error(`Unsupported architecture on Windows: ${arch}`) - } - break - case 'darwin': - localFileExisted = existsSync(join(__dirname, 'y-octo.darwin-universal.node')) - try { - if (localFileExisted) { - nativeBinding = require('./y-octo.darwin-universal.node') - } else { - nativeBinding = require('@y-octo/node-darwin-universal') - } - break - } catch {} - switch (arch) { - case 'x64': - localFileExisted = existsSync(join(__dirname, 'y-octo.darwin-x64.node')) - try { - if (localFileExisted) { - nativeBinding = require('./y-octo.darwin-x64.node') - } else { - nativeBinding = require('@y-octo/node-darwin-x64') - } - } catch (e) { - loadError = e - } - break - case 'arm64': - localFileExisted = existsSync( - join(__dirname, 'y-octo.darwin-arm64.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./y-octo.darwin-arm64.node') - } else { - nativeBinding = require('@y-octo/node-darwin-arm64') - } - } catch (e) { - loadError = e - } - break - default: - throw new Error(`Unsupported architecture on macOS: ${arch}`) - } - break - case 'freebsd': - if (arch !== 'x64') { - throw new Error(`Unsupported architecture on FreeBSD: ${arch}`) - } - localFileExisted = existsSync(join(__dirname, 'y-octo.freebsd-x64.node')) - try { - if (localFileExisted) { - nativeBinding = require('./y-octo.freebsd-x64.node') - } else { - nativeBinding = require('@y-octo/node-freebsd-x64') - } - } catch (e) { - loadError = e - } - break - case 'linux': - switch (arch) { - case 'x64': - if (isMusl()) { - localFileExisted = existsSync( - join(__dirname, 'y-octo.linux-x64-musl.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./y-octo.linux-x64-musl.node') - } else { - nativeBinding = require('@y-octo/node-linux-x64-musl') - } - } catch (e) { - loadError = e - } - } else { - localFileExisted = existsSync( - join(__dirname, 'y-octo.linux-x64-gnu.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./y-octo.linux-x64-gnu.node') - } else { - nativeBinding = require('@y-octo/node-linux-x64-gnu') - } - } catch (e) { - loadError = e - } - } - break - case 'arm64': - if (isMusl()) { - localFileExisted = existsSync( - join(__dirname, 'y-octo.linux-arm64-musl.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./y-octo.linux-arm64-musl.node') - } else { - nativeBinding = require('@y-octo/node-linux-arm64-musl') - } - } catch (e) { - loadError = e - } - } else { - localFileExisted = existsSync( - join(__dirname, 'y-octo.linux-arm64-gnu.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./y-octo.linux-arm64-gnu.node') - } else { - nativeBinding = require('@y-octo/node-linux-arm64-gnu') - } - } catch (e) { - loadError = e - } - } - break - case 'arm': - localFileExisted = existsSync( - join(__dirname, 'y-octo.linux-arm-gnueabihf.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./y-octo.linux-arm-gnueabihf.node') - } else { - nativeBinding = require('@y-octo/node-linux-arm-gnueabihf') - } - } catch (e) { - loadError = e - } - break - default: - throw new Error(`Unsupported architecture on Linux: ${arch}`) - } - break - default: - throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`) -} - -if (!nativeBinding) { - if (loadError) { - throw loadError - } - throw new Error(`Failed to load native binding`) -} - -const { YArray, YArrayIterator, Awareness, Doc, encodeStateAsUpdate, encodeStateVector, compareStructStores, compareIds, createDeleteSetFromStructStore, equalDeleteSets, snapshot, encodeSnapshot, applyUpdate, mergeUpdates, isAbstractType, YMap, YMapEntriesIterator, YMapKeyIterator, YMapValuesIterator, YText, Id, Store, DeleteSet, YSnapshot } = nativeBinding - -module.exports.YArray = YArray -module.exports.YArrayIterator = YArrayIterator -module.exports.Awareness = Awareness -module.exports.Doc = Doc -module.exports.encodeStateAsUpdate = encodeStateAsUpdate -module.exports.encodeStateVector = encodeStateVector -module.exports.compareStructStores = compareStructStores -module.exports.compareIds = compareIds -module.exports.createDeleteSetFromStructStore = createDeleteSetFromStructStore -module.exports.equalDeleteSets = equalDeleteSets -module.exports.snapshot = snapshot -module.exports.encodeSnapshot = encodeSnapshot -module.exports.applyUpdate = applyUpdate -module.exports.mergeUpdates = mergeUpdates -module.exports.isAbstractType = isAbstractType -module.exports.YMap = YMap -module.exports.YMapEntriesIterator = YMapEntriesIterator -module.exports.YMapKeyIterator = YMapKeyIterator -module.exports.YMapValuesIterator = YMapValuesIterator -module.exports.YText = YText -module.exports.Id = Id -module.exports.Store = Store -module.exports.DeleteSet = DeleteSet -module.exports.YSnapshot = YSnapshot +export * from "./yocto"; diff --git a/y-octo-node/package.json b/y-octo-node/package.json index 5475922..bff70f4 100644 --- a/y-octo-node/package.json +++ b/y-octo-node/package.json @@ -20,7 +20,7 @@ }, "license": "MIT", "devDependencies": { - "@napi-rs/cli": "^2.16.2", + "@napi-rs/cli": "^2.18.4", "@types/node": "^20.16.4", "@types/prompts": "^2.4.4", "ava": "^6.1.3", @@ -36,8 +36,8 @@ }, "scripts": { "artifacts": "napi artifacts", - "build": "napi build --platform --release --no-const-enum", - "build:debug": "napi build --platform --no-const-enum", + "build": "napi build --platform --release --no-const-enum --dts yocto.d.ts --js yocto.js", + "build:debug": "napi build --platform --no-const-enum --dts yocto.d.ts --js yocto.js", "universal": "napi universal", "test": "ava --concurrency 1 --serial", "test:watch": "yarn exec tsx ./scripts/run-test.mts all --watch", diff --git a/y-octo-node/tests/array.spec.ts b/y-octo-node/tests/array.spec.ts index a9d15ab..61918f7 100644 --- a/y-octo-node/tests/array.spec.ts +++ b/y-octo-node/tests/array.spec.ts @@ -1,6 +1,6 @@ import test from "ava"; -import * as YOcto from "../yocto"; +import * as YOcto from "../index"; let client_id: number; let doc: YOcto.Doc; diff --git a/y-octo-node/tests/doc.spec.ts b/y-octo-node/tests/doc.spec.ts index 3a3c844..2a46c2c 100644 --- a/y-octo-node/tests/doc.spec.ts +++ b/y-octo-node/tests/doc.spec.ts @@ -1,6 +1,6 @@ import test from "ava"; -import * as YOcto from "../yocto"; +import * as YOcto from "../index"; import * as Y from "yjs"; let client_id: number; diff --git a/y-octo-node/tests/map.spec.ts b/y-octo-node/tests/map.spec.ts index eb33490..534a813 100644 --- a/y-octo-node/tests/map.spec.ts +++ b/y-octo-node/tests/map.spec.ts @@ -1,7 +1,7 @@ import test from "ava"; import * as Y from "yjs"; -import * as YOcto from "../yocto"; +import * as YOcto from "../index"; let client_id: number; let doc: YOcto.Doc; diff --git a/y-octo-node/tests/text.spec.ts b/y-octo-node/tests/text.spec.ts index e38c73e..dbe6b42 100644 --- a/y-octo-node/tests/text.spec.ts +++ b/y-octo-node/tests/text.spec.ts @@ -1,6 +1,6 @@ import test from "ava"; -import * as YOcto from "../yocto"; +import * as YOcto from "../index"; let client_id: number; let doc: YOcto.Doc; diff --git a/y-octo-node/tests/yjs/testHelper.ts b/y-octo-node/tests/yjs/testHelper.ts index 7d32177..225da7d 100644 --- a/y-octo-node/tests/yjs/testHelper.ts +++ b/y-octo-node/tests/yjs/testHelper.ts @@ -5,7 +5,7 @@ import * as decoding from "lib0/decoding"; import * as syncProtocol from "y-protocols/sync"; import * as object from "lib0/object"; import * as map from "lib0/map"; -import * as Y from "../../yocto"; +import * as Y from "../../index"; if (typeof window !== "undefined") { // @ts-ignore diff --git a/y-octo-node/tests/yjs/y-array.spec.ts b/y-octo-node/tests/yjs/y-array.spec.ts index edb73fa..2b88b9c 100644 --- a/y-octo-node/tests/yjs/y-array.spec.ts +++ b/y-octo-node/tests/yjs/y-array.spec.ts @@ -2,7 +2,7 @@ import test from "ava"; import { init, compare, applyRandomTests } from "./testHelper"; -import * as Y from "../../yocto"; +import * as Y from "../../index"; import * as prng from "lib0/prng"; import * as math from "lib0/math"; import { randomInt } from "node:crypto"; diff --git a/y-octo-node/tests/yjs/y-map.spec.ts b/y-octo-node/tests/yjs/y-map.spec.ts index 4dda044..4e74e9c 100644 --- a/y-octo-node/tests/yjs/y-map.spec.ts +++ b/y-octo-node/tests/yjs/y-map.spec.ts @@ -4,7 +4,7 @@ import test from "ava"; import { init, compare, applyRandomTests } from "./testHelper.js"; -import * as Y from "../../yocto"; +import * as Y from "../../index"; import * as prng from "lib0/prng"; const production = false; diff --git a/y-octo-node/tests/yjs/y-text.spec.ts b/y-octo-node/tests/yjs/y-text.spec.ts index 0be9b16..e5f74ed 100644 --- a/y-octo-node/tests/yjs/y-text.spec.ts +++ b/y-octo-node/tests/yjs/y-text.spec.ts @@ -5,7 +5,7 @@ import test from "ava"; import * as prng from "lib0/prng"; import * as math from "lib0/math"; -import * as Y from "../../yocto"; +import * as Y from "../../index"; import { applyRandomTests, compare, init } from "./testHelper"; let gen: prng.PRNG; diff --git a/y-octo-node/yocto.d.ts b/y-octo-node/yocto.d.ts index 02f67a4..4f3ffbc 100644 --- a/y-octo-node/yocto.d.ts +++ b/y-octo-node/yocto.d.ts @@ -1,8 +1,108 @@ -import * as Y from "./index"; +/* tslint:disable */ +/* eslint-disable */ -export class Doc extends Y.Doc {} -export class Array extends Y.YArray {} -export class Map extends Y.YMap {} -export class Text extends Y.YText {} +/* auto-generated by NAPI-RS */ -export * from "./index"; +export declare function encodeStateAsUpdate(doc: Doc, state?: Buffer | undefined | null): Buffer +export declare function encodeStateVector(doc: Doc): Buffer +export declare function compareStructStores(store: YStore, other: YStore): boolean +export declare function compareIds(a?: YId | undefined | null, b?: YId | undefined | null): boolean +export declare function createDeleteSetFromStructStore(store: YStore): YDeleteSet +export declare function equalDeleteSets(a: YDeleteSet, b: YDeleteSet): boolean +export declare function snapshot(doc: Doc): YSnapshot +export declare function encodeSnapshot(snapshot: YSnapshot): Buffer +export declare function applyUpdate(doc: Doc, update: Buffer): void +export declare function mergeUpdates(updates: Array): Buffer +export declare function isAbstractType(unknown: unknown): boolean +export declare class YArray { + static from(array: JsArray): this + get length(): number + get isEmpty(): boolean + get(index: number): T + slice(start: number, end?: number | undefined | null): Array + map(callback: (...args: any[]) => any): Array + insert(index: number, value: YArray | YMap | YText | boolean | number | string | Record | null | undefined): void + push(value: YArray | YMap | YText | boolean | number | string | Record | null | undefined): void + unshift(value: YArray | YMap | YText | boolean | number | string | Record | null | undefined): void + delete(index: number, len?: number | undefined | null): void + iter(): YArrayIterator + toArray(): JsArray + toJSON(): JsArray + observe(callback: (...args: any[]) => any): void + observeDeep(callback: (...args: any[]) => any): void +} +export declare class YArrayIterator { + [Symbol.iterator](): Iterator +} +export type YAwareness = Awareness +export declare class Awareness { + constructor(clientId?: number | undefined | null) + get clientId(): number + get states>(): Record +} +export type YDoc = Doc +export declare class Doc { + constructor(clientId?: number | undefined | null) + get clientId(): number + set clientId(clientId: number) + get guid(): string + get store(): YStore + get keys(): Array + getOrCreateArray(key: string): YArray + getOrCreateText(key: string): YText + getOrCreateMap(key: string): YMap + createArray(): YArray + createText(text?: string | undefined | null): YText + createMap(entries?: JsArray | undefined | null): YMap + applyUpdate(update: Buffer): void + encodeStateAsUpdateV1(state?: Buffer | undefined | null): Buffer + gc(): void + onUpdate(callback: (result: Uint8Array) => void): void + offUpdate(): void + transact(callback: (...args: any[]) => any): void +} +export declare class YMap { + get length(): number + get size(): number + get isEmpty(): boolean + get itemId(): YId | null + get(key: string): T + set | null | undefined>(key: string, value: T): T + delete(key: string): void + clear(): void + toJson(): object + entries(): YMapEntriesIterator + keys(): YMapKeyIterator + values(): YMapValuesIterator + observe(callback: (...args: any[]) => any): void + observeDeep(callback: (...args: any[]) => any): void +} +export declare class YMapEntriesIterator { + [Symbol.iterator](): Iterator +} +export declare class YMapKeyIterator { + [Symbol.iterator](): Iterator +} +export declare class YMapValuesIterator { + [Symbol.iterator](): Iterator +} +export declare class YText { + constructor() + get len(): number + get isEmpty(): boolean + insert(index: number, str: string): void + delete(index: number, len: number): void + get length(): number + applyDelta(delta: JsArray): void + toDelta(): JsArray + toString(): string + observe(callback: (...args: any[]) => any): void + observeDeep(callback: (...args: any[]) => any): void +} +export type YId = Id +export declare class Id { } +export type YStore = Store +export declare class Store { } +export type YDeleteSet = DeleteSet +export declare class DeleteSet { } +export declare class YSnapshot { } diff --git a/y-octo-node/yocto.js b/y-octo-node/yocto.js index f6dc11c..9c6ba63 100644 --- a/y-octo-node/yocto.js +++ b/y-octo-node/yocto.js @@ -1,8 +1,338 @@ -import * as Y from "./index"; +/* tslint:disable */ +/* eslint-disable */ +/* prettier-ignore */ -export const Doc = Y.Doc; -export const Array = Y.YArray; -export const Map = Y.YMap; -export const Text = Y.YText; +/* auto-generated by NAPI-RS */ -export * from "./index"; +const { existsSync, readFileSync } = require('fs') +const { join } = require('path') + +const { platform, arch } = process + +let nativeBinding = null +let localFileExisted = false +let loadError = null + +function isMusl() { + // For Node 10 + if (!process.report || typeof process.report.getReport !== 'function') { + try { + const lddPath = require('child_process').execSync('which ldd').toString().trim() + return readFileSync(lddPath, 'utf8').includes('musl') + } catch (e) { + return true + } + } else { + const { glibcVersionRuntime } = process.report.getReport().header + return !glibcVersionRuntime + } +} + +switch (platform) { + case 'android': + switch (arch) { + case 'arm64': + localFileExisted = existsSync(join(__dirname, 'y-octo.android-arm64.node')) + try { + if (localFileExisted) { + nativeBinding = require('./y-octo.android-arm64.node') + } else { + nativeBinding = require('@y-octo/node-android-arm64') + } + } catch (e) { + loadError = e + } + break + case 'arm': + localFileExisted = existsSync(join(__dirname, 'y-octo.android-arm-eabi.node')) + try { + if (localFileExisted) { + nativeBinding = require('./y-octo.android-arm-eabi.node') + } else { + nativeBinding = require('@y-octo/node-android-arm-eabi') + } + } catch (e) { + loadError = e + } + break + default: + throw new Error(`Unsupported architecture on Android ${arch}`) + } + break + case 'win32': + switch (arch) { + case 'x64': + localFileExisted = existsSync( + join(__dirname, 'y-octo.win32-x64-msvc.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./y-octo.win32-x64-msvc.node') + } else { + nativeBinding = require('@y-octo/node-win32-x64-msvc') + } + } catch (e) { + loadError = e + } + break + case 'ia32': + localFileExisted = existsSync( + join(__dirname, 'y-octo.win32-ia32-msvc.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./y-octo.win32-ia32-msvc.node') + } else { + nativeBinding = require('@y-octo/node-win32-ia32-msvc') + } + } catch (e) { + loadError = e + } + break + case 'arm64': + localFileExisted = existsSync( + join(__dirname, 'y-octo.win32-arm64-msvc.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./y-octo.win32-arm64-msvc.node') + } else { + nativeBinding = require('@y-octo/node-win32-arm64-msvc') + } + } catch (e) { + loadError = e + } + break + default: + throw new Error(`Unsupported architecture on Windows: ${arch}`) + } + break + case 'darwin': + localFileExisted = existsSync(join(__dirname, 'y-octo.darwin-universal.node')) + try { + if (localFileExisted) { + nativeBinding = require('./y-octo.darwin-universal.node') + } else { + nativeBinding = require('@y-octo/node-darwin-universal') + } + break + } catch {} + switch (arch) { + case 'x64': + localFileExisted = existsSync(join(__dirname, 'y-octo.darwin-x64.node')) + try { + if (localFileExisted) { + nativeBinding = require('./y-octo.darwin-x64.node') + } else { + nativeBinding = require('@y-octo/node-darwin-x64') + } + } catch (e) { + loadError = e + } + break + case 'arm64': + localFileExisted = existsSync( + join(__dirname, 'y-octo.darwin-arm64.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./y-octo.darwin-arm64.node') + } else { + nativeBinding = require('@y-octo/node-darwin-arm64') + } + } catch (e) { + loadError = e + } + break + default: + throw new Error(`Unsupported architecture on macOS: ${arch}`) + } + break + case 'freebsd': + if (arch !== 'x64') { + throw new Error(`Unsupported architecture on FreeBSD: ${arch}`) + } + localFileExisted = existsSync(join(__dirname, 'y-octo.freebsd-x64.node')) + try { + if (localFileExisted) { + nativeBinding = require('./y-octo.freebsd-x64.node') + } else { + nativeBinding = require('@y-octo/node-freebsd-x64') + } + } catch (e) { + loadError = e + } + break + case 'linux': + switch (arch) { + case 'x64': + if (isMusl()) { + localFileExisted = existsSync( + join(__dirname, 'y-octo.linux-x64-musl.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./y-octo.linux-x64-musl.node') + } else { + nativeBinding = require('@y-octo/node-linux-x64-musl') + } + } catch (e) { + loadError = e + } + } else { + localFileExisted = existsSync( + join(__dirname, 'y-octo.linux-x64-gnu.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./y-octo.linux-x64-gnu.node') + } else { + nativeBinding = require('@y-octo/node-linux-x64-gnu') + } + } catch (e) { + loadError = e + } + } + break + case 'arm64': + if (isMusl()) { + localFileExisted = existsSync( + join(__dirname, 'y-octo.linux-arm64-musl.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./y-octo.linux-arm64-musl.node') + } else { + nativeBinding = require('@y-octo/node-linux-arm64-musl') + } + } catch (e) { + loadError = e + } + } else { + localFileExisted = existsSync( + join(__dirname, 'y-octo.linux-arm64-gnu.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./y-octo.linux-arm64-gnu.node') + } else { + nativeBinding = require('@y-octo/node-linux-arm64-gnu') + } + } catch (e) { + loadError = e + } + } + break + case 'arm': + if (isMusl()) { + localFileExisted = existsSync( + join(__dirname, 'y-octo.linux-arm-musleabihf.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./y-octo.linux-arm-musleabihf.node') + } else { + nativeBinding = require('@y-octo/node-linux-arm-musleabihf') + } + } catch (e) { + loadError = e + } + } else { + localFileExisted = existsSync( + join(__dirname, 'y-octo.linux-arm-gnueabihf.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./y-octo.linux-arm-gnueabihf.node') + } else { + nativeBinding = require('@y-octo/node-linux-arm-gnueabihf') + } + } catch (e) { + loadError = e + } + } + break + case 'riscv64': + if (isMusl()) { + localFileExisted = existsSync( + join(__dirname, 'y-octo.linux-riscv64-musl.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./y-octo.linux-riscv64-musl.node') + } else { + nativeBinding = require('@y-octo/node-linux-riscv64-musl') + } + } catch (e) { + loadError = e + } + } else { + localFileExisted = existsSync( + join(__dirname, 'y-octo.linux-riscv64-gnu.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./y-octo.linux-riscv64-gnu.node') + } else { + nativeBinding = require('@y-octo/node-linux-riscv64-gnu') + } + } catch (e) { + loadError = e + } + } + break + case 's390x': + localFileExisted = existsSync( + join(__dirname, 'y-octo.linux-s390x-gnu.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./y-octo.linux-s390x-gnu.node') + } else { + nativeBinding = require('@y-octo/node-linux-s390x-gnu') + } + } catch (e) { + loadError = e + } + break + default: + throw new Error(`Unsupported architecture on Linux: ${arch}`) + } + break + default: + throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`) +} + +if (!nativeBinding) { + if (loadError) { + throw loadError + } + throw new Error(`Failed to load native binding`) +} + +const { YArray, YArrayIterator, Awareness, Doc, encodeStateAsUpdate, encodeStateVector, compareStructStores, compareIds, createDeleteSetFromStructStore, equalDeleteSets, snapshot, encodeSnapshot, applyUpdate, mergeUpdates, isAbstractType, YMap, YMapEntriesIterator, YMapKeyIterator, YMapValuesIterator, YText, Id, Store, DeleteSet, YSnapshot } = nativeBinding + +module.exports.YArray = YArray +module.exports.YArrayIterator = YArrayIterator +module.exports.Awareness = Awareness +module.exports.Doc = Doc +module.exports.encodeStateAsUpdate = encodeStateAsUpdate +module.exports.encodeStateVector = encodeStateVector +module.exports.compareStructStores = compareStructStores +module.exports.compareIds = compareIds +module.exports.createDeleteSetFromStructStore = createDeleteSetFromStructStore +module.exports.equalDeleteSets = equalDeleteSets +module.exports.snapshot = snapshot +module.exports.encodeSnapshot = encodeSnapshot +module.exports.applyUpdate = applyUpdate +module.exports.mergeUpdates = mergeUpdates +module.exports.isAbstractType = isAbstractType +module.exports.YMap = YMap +module.exports.YMapEntriesIterator = YMapEntriesIterator +module.exports.YMapKeyIterator = YMapKeyIterator +module.exports.YMapValuesIterator = YMapValuesIterator +module.exports.YText = YText +module.exports.Id = Id +module.exports.Store = Store +module.exports.DeleteSet = DeleteSet +module.exports.YSnapshot = YSnapshot diff --git a/yarn.lock b/yarn.lock index 1a5c320..05a1cfc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -237,12 +237,12 @@ __metadata: languageName: node linkType: hard -"@napi-rs/cli@npm:^2.16.2": - version: 2.16.3 - resolution: "@napi-rs/cli@npm:2.16.3" +"@napi-rs/cli@npm:^2.18.4": + version: 2.18.4 + resolution: "@napi-rs/cli@npm:2.18.4" bin: napi: scripts/index.js - checksum: 11f78b09548bc5c22df56e4fab4a87b68c2d3f2a18a55cf11e775e6a4cb5739ec0e21a14e614db2cdc2b9773cb42536c6bd00c3f85d3b461f956594f8a89ddcb + checksum: f243e5c822a4a9103fba49193eda2023cb08c1ef9c0b521d8a0ece860ef13f9f2b4d5ac106c3d9e5792d22ed2196213e336cf59d9c2ee81241ac4b4b8ca6ea30 languageName: node linkType: hard @@ -406,7 +406,7 @@ __metadata: version: 0.0.0-use.local resolution: "@y-octo/node@workspace:y-octo-node" dependencies: - "@napi-rs/cli": ^2.16.2 + "@napi-rs/cli": ^2.18.4 "@types/node": ^20.16.4 "@types/prompts": ^2.4.4 ava: ^6.1.3 From ceef5e0b4e2507bfe3a09580487f7529d7e1d4ed Mon Sep 17 00:00:00 2001 From: DarkSky Date: Thu, 29 Aug 2024 17:17:01 +0800 Subject: [PATCH 32/75] feat: switch to js wrapper --- .prettierignore | 4 +- y-octo-node/index.d.ts | 8 -- y-octo-node/index.js | 8 -- y-octo-node/index.ts | 258 +++++++++++++++++++++++++++++++++++++++ y-octo-node/src/array.rs | 244 ++++++------------------------------ y-octo-node/src/doc.rs | 8 +- y-octo-node/src/map.rs | 31 ++--- y-octo-node/src/utils.rs | 12 +- y-octo-node/yocto.d.ts | 1 - 9 files changed, 321 insertions(+), 253 deletions(-) delete mode 100644 y-octo-node/index.d.ts delete mode 100644 y-octo-node/index.js create mode 100644 y-octo-node/index.ts diff --git a/.prettierignore b/.prettierignore index 67d217b..73c4f0a 100644 --- a/.prettierignore +++ b/.prettierignore @@ -4,6 +4,6 @@ target y-octo/README.md .cargo vendor -index.d.ts -index.js +yocto.d.ts +yocto.js .coverage \ No newline at end of file diff --git a/y-octo-node/index.d.ts b/y-octo-node/index.d.ts deleted file mode 100644 index 43c09a4..0000000 --- a/y-octo-node/index.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -import * as Y from "./yocto"; - -export class Doc extends Y.Doc {} -export class Array extends Y.YArray {} -export class Map extends Y.YMap {} -export class Text extends Y.YText {} - -export * from "./yocto"; diff --git a/y-octo-node/index.js b/y-octo-node/index.js deleted file mode 100644 index d61a952..0000000 --- a/y-octo-node/index.js +++ /dev/null @@ -1,8 +0,0 @@ -import * as Y from "./yocto"; - -export const Doc = Y.Doc; -export const Array = Y.YArray; -export const Map = Y.YMap; -export const Text = Y.YText; - -export * from "./yocto"; diff --git a/y-octo-node/index.ts b/y-octo-node/index.ts new file mode 100644 index 0000000..6c4e4e5 --- /dev/null +++ b/y-octo-node/index.ts @@ -0,0 +1,258 @@ +import * as Y from "./yocto"; + +type ArrayType = + | string + | number + | any[] + | Uint8Array + | { [x: string]: any } + | null; +type ListItem = + | Array + | Map + | Text + | boolean + | number + | string + | Record + | null; + +export const Doc = Y.Doc; + +export class Array { + private ytype?: { doc: Y.Doc; array: Y.YArray }; + private preliminary: any[] = []; + + static from(items: T[]): Array { + return new Array(items); + } + + constructor(items: ArrayType[], ydoc?: Y.YDoc) { + this.preliminary = items; + if (ydoc) this.integrate(ydoc); + } + + integrate(ydoc: Y.YDoc): Y.YArray { + if (!this.ytype) { + this.ytype = { doc: ydoc, array: ydoc.createArray() }; + for (const item of this.preliminary) { + if (item instanceof Array) { + this.ytype.array.push(item.integrate(ydoc)); + } else { + this.ytype.array.push(item); + } + } + this.preliminary = []; + } + return this.ytype.array; + } + + get length(): number { + return this.ytype ? this.ytype.array.length : this.preliminary.length; + } + + get isEmpty(): boolean { + return this.ytype + ? this.ytype.array.isEmpty + : this.preliminary.length === 0; + } + + get(index: number): T { + return this.ytype ? this.ytype.array.get(index) : this.preliminary[index]; + } + + slice(start: number, end?: number): T[] { + return this.ytype + ? this.ytype.array.slice(start, end) + : this.preliminary.slice(start, end); + } + + map(callback: (...args: any[]) => any): T[] { + return this.ytype + ? this.ytype.array.map(callback) + : this.preliminary.map(callback); + } + + insert(index: number, value: ListItem): void { + if (this.ytype) { + if (value instanceof Array || value instanceof Map) { + this.ytype.array.insert(index, value.integrate(this.ytype.doc)); + } else { + this.ytype.array.insert(index, value); + } + } else { + this.preliminary.splice(index, 0, value); + } + } + + push(value?: ListItem): void { + if (this.ytype) { + this.ytype.array.push(value); + } else { + this.preliminary.push(value); + } + } + + unshift(value?: ListItem): void { + if (this.ytype) { + this.ytype.array.unshift(value); + } else { + this.preliminary.unshift(value); + } + } + + delete(index: number, len?: number): void { + if (this.ytype) { + this.ytype.array.delete(index, len); + } else { + this.preliminary.splice(index, len); + } + } + + iter(): Y.YArrayIterator { + return this.ytype + ? this.ytype.array.iter() + : this.preliminary[Symbol.iterator](); + } + + toArray(): any[] { + return this.ytype ? this.ytype.array.toArray() : this.preliminary; + } + + toJSON(): any[] { + return this.ytype ? this.ytype.array.toJSON() : this.preliminary; + } + + observe(callback: (...args: any[]) => any): void { + if (this.ytype) { + this.ytype.array.observe(callback); + } else { + throw new Error("Not implemented"); + } + } + + observeDeep(callback: (...args: any[]) => any): void { + if (this.ytype) { + this.ytype.array.observeDeep(callback); + } else { + throw new Error("Not implemented"); + } + } +} + +export class Map { + private ytype?: { doc: Y.Doc; map: Y.YMap }; + private preliminary: Record = {}; + + constructor(items: Record, ydoc?: Y.YDoc) { + this.preliminary = items; + if (ydoc) this.integrate(ydoc); + } + + integrate(ydoc: Y.YDoc): Y.YMap { + if (!this.ytype) { + this.ytype = { doc: ydoc, map: ydoc.createMap() }; + for (const [key, val] of Object.entries(this.preliminary)) { + if (val instanceof Array) { + this.ytype.map.set(key, val.integrate(ydoc)); + } else { + this.ytype.map.set(key, val); + } + } + this.preliminary = {}; + } + return this.ytype.map; + } + + get length(): number { + return this.ytype + ? this.ytype.map.length + : Object.keys(this.preliminary).length; + } + + get size(): number { + return this.length; + } + + get isEmpty(): boolean { + return this.ytype ? this.ytype.map.isEmpty : this.length === 0; + } + + get itemId(): Y.YId | null { + return this.ytype?.map.itemId || null; + } + + get(key: string): T { + return this.ytype ? this.ytype.map.get(key) : this.preliminary[key]; + } + + set(key: string, value: T) { + if (this.ytype) { + if (value instanceof Array || value instanceof Map) { + this.ytype.map.set(key, value.integrate(this.ytype.doc)); + } else { + this.ytype.map.set(key, value); + } + } else { + this.preliminary[key] = value; + } + } + + delete(key: string): void { + if (this.ytype) { + this.ytype.map.delete(key); + } else { + delete this.preliminary[key]; + } + } + + clear(): void { + if (this.ytype) { + this.ytype.map.clear(); + } else { + this.preliminary = {}; + } + } + + toJson(): Record { + return this.ytype + ? this.ytype.map.toJson() + : JSON.parse(JSON.stringify(this.preliminary)); + } + + entries(): Y.YMapEntriesIterator { + return this.ytype + ? this.ytype.map.entries() + : Object.entries(this.preliminary); + } + + keys(): Y.YMapKeyIterator { + return this.ytype ? this.ytype.map.keys() : Object.keys(this.preliminary); + } + + values(): Y.YMapValuesIterator { + return this.ytype + ? this.ytype.map.values() + : Object.values(this.preliminary); + } + + observe(callback: (...args: any[]) => any): void { + if (this.ytype) { + this.ytype.map.observe(callback); + } else { + throw new Error("Not implemented"); + } + } + + observeDeep(callback: (...args: any[]) => any): void { + if (this.ytype) { + this.ytype.map.observeDeep(callback); + } else { + throw new Error("Not implemented"); + } + } +} + +export class Text extends Y.YText {} + +export * from "./yocto"; diff --git a/y-octo-node/src/array.rs b/y-octo-node/src/array.rs index 41627ca..3d2fd87 100644 --- a/y-octo-node/src/array.rs +++ b/y-octo-node/src/array.rs @@ -1,200 +1,48 @@ -use std::{ - ops::DerefMut, - sync::{Arc, RwLock}, -}; - use napi::{ bindgen_prelude::{Array as JsArray, FromNapiValue}, iterator::Generator, Env, JsFunction, JsUnknown, ValueType, }; -use y_octo::{Any, Array, Doc, JwstCodecResult, Value}; +use y_octo::{Any, Array, Value}; use super::*; -fn cannot_insert_nested_ytype() -> anyhow::Error { - anyhow::Error::msg("Cannot insert nested y-type before current raw-type integrated") -} - -#[derive(Clone)] -pub(crate) enum ManagedArray { - YArray { array: Array, doc: Doc }, - JsArray(Vec), -} - -impl TryFrom for ManagedArray { - type Error = anyhow::Error; - - fn try_from(array: JsArray) -> Result { - match array - .coerce_to_object() - .and_then(|o| o.get_array_length().map(|l| (o, l))) - { - Ok((object, length)) => { - let mut array = vec![]; - for i in 0..length { - if let Ok(unknown) = object.get_element::(i) { - match get_any_from_js_unknown(unknown) { - Ok(any) => array.push(any), - Err(e) => { - return Err( - anyhow::Error::new(e).context(format!("Failed to coerce value to any: {}", i)) - ) - } - } - } - } - Ok(Self::JsArray(array)) - } - Err(e) => Err(anyhow::Error::new(e).context("Failed to coerce value to array")), - } - } -} - -impl ManagedArray { - fn integrate_to_ytype(&mut self, doc: &Doc) -> JwstCodecResult { - match self { - Self::YArray { array, .. } => Ok(array.clone()), - Self::JsArray(array) => { - let mut yarray = doc.create_array()?; - for value in array { - yarray.push(value.clone())?; - } - *self = Self::YArray { - array: yarray.clone(), - doc: doc.clone(), - }; - Ok(yarray) - } - } - } - - fn doc(&self) -> Option<&Doc> { - match self { - Self::YArray { doc, .. } => Some(doc), - Self::JsArray(_) => None, - } - } - - fn len(&self) -> u64 { - match self { - Self::YArray { array, .. } => array.len(), - Self::JsArray(array) => array.len() as u64, - } - } - - fn is_empty(&self) -> bool { - match self { - Self::YArray { array, .. } => array.is_empty(), - Self::JsArray(array) => array.is_empty(), - } - } - - fn get(&self, env: Env, index: i64) -> Result { - match self { - Self::YArray { array, doc } => { - if let Some(value) = array.get(index as u64) { - match value { - Value::Any(any) => get_js_unknown_from_any(env, any).map(MixedYType::D), - Value::Array(array) => Ok(MixedYType::A(YArray::inner_new(array, doc))), - Value::Map(map) => Ok(MixedYType::B(YMap::inner_new(map, doc))), - Value::Text(text) => Ok(MixedYType::C(YText::inner_new(text))), - _ => env.get_null().map(|v| v.into_unknown()).map(MixedYType::D), - } - .map_err(anyhow::Error::from) - } else { - Ok(MixedYType::D(env.get_null()?.into_unknown())) - } - } - Self::JsArray(array) => array - .get(index as usize) - .ok_or_else(|| anyhow::Error::msg("Failed to get value from array")) - .and_then(|any| { - get_js_unknown_from_any(env, any.clone()) - .map(MixedYType::D) - .map_err(anyhow::Error::from) - }), - } - } - - fn iter(&self) -> Box + '_> { - match self { - Self::YArray { array, .. } => Box::new(array.iter().map(|v| v.into())), - Self::JsArray(array) => Box::new(array.iter().map(|v| Value::Any(v.clone()))), - } - } - - fn insert>(&mut self, index: u64, value: V) -> Result<(), anyhow::Error> { - match self { - Self::YArray { array, .. } => array.insert(index, value).map_err(anyhow::Error::from), - Self::JsArray(array) => match value.into() { - Value::Any(any) => Ok(array.insert(index as usize, any)), - Value::Array(_) | Value::Map(_) | Value::Text(_) => Err(cannot_insert_nested_ytype()), - _ => Err(anyhow::Error::msg("Unsupported value type")), - }, - } - } - - fn remove(&mut self, index: u64, len: u64) -> Result<(), anyhow::Error> { - match self { - Self::YArray { array, .. } => array.remove(index, len).map_err(anyhow::Error::from), - Self::JsArray(array) => { - if index >= array.len() as u64 { - return Err(anyhow::Error::msg("Index out of bounds")); - } else if index + len > array.len() as u64 { - return Err(anyhow::Error::msg("Length out of bounds")); - } else if len == 0 { - return Ok(()); - } - - array.drain((index as usize)..(index + len) as usize); - Ok(()) - } - } - } -} - #[napi] #[derive(Clone)] pub struct YArray { - pub(crate) array: Arc>, + pub(crate) array: Array, } #[napi] impl YArray { - pub(crate) fn inner_new(array: Array, doc: &Doc) -> Self { - Self { - array: Arc::new(RwLock::new(ManagedArray::YArray { - array, - doc: doc.clone(), - })), - } - } - - #[napi] - pub fn from(array: JsArray) -> Result { - Ok(Self { - array: Arc::new(RwLock::new(array.try_into()?)), - }) - } - - pub(crate) fn integrate_to_ytype(&self, doc: &Doc) -> JwstCodecResult { - self.array.write().unwrap().integrate_to_ytype(doc) + pub(crate) fn inner_new(array: Array) -> Self { + Self { array } } #[napi(getter)] pub fn length(&self) -> i64 { - self.array.read().unwrap().len() as i64 + self.array.len() as i64 } #[napi(getter)] pub fn is_empty(&self) -> bool { - self.array.read().unwrap().is_empty() + self.array.is_empty() } #[napi(ts_generic_types = "T = unknown", ts_return_type = "T")] pub fn get(&self, env: Env, index: i64) -> Result { - self.array.read().unwrap().get(env, index) + if let Some(value) = self.array.get(index as u64) { + match value { + Value::Any(any) => get_js_unknown_from_any(env, any).map(MixedYType::D), + Value::Array(array) => Ok(MixedYType::A(YArray::inner_new(array))), + Value::Map(map) => Ok(MixedYType::B(YMap::inner_new(map))), + Value::Text(text) => Ok(MixedYType::C(YText::inner_new(text))), + _ => env.get_null().map(|v| v.into_unknown()).map(MixedYType::D), + } + .map_err(anyhow::Error::from) + } else { + Ok(MixedYType::D(env.get_null()?.into_unknown())) + } } #[napi(ts_generic_types = "T = unknown", ts_return_type = "Array")] @@ -214,10 +62,8 @@ impl YArray { } }) .unwrap_or(self.length() - start) as usize; - let array = self.array.read().unwrap(); - let doc = array.doc(); - for value in array.iter().skip(start as usize).take(end) { - js_array.insert(get_js_unknown_from_value(env, doc, value)?)?; + for value in self.array.iter().skip(start as usize).take(end) { + js_array.insert(get_js_unknown_from_value(env, value)?)?; } Ok(js_array) } @@ -225,10 +71,8 @@ impl YArray { #[napi(ts_generic_types = "T = unknown", ts_return_type = "Array")] pub fn map(&self, env: Env, callback: JsFunction) -> Result { let mut js_array = env.create_array(0)?; - let array = self.array.read().unwrap(); - let doc = array.doc(); - for value in array.iter() { - let js_value = get_js_unknown_from_value(env, doc, value)?; + for value in self.array.iter() { + let js_value = get_js_unknown_from_value(env, value)?; let result = callback.call(None, &[js_value.into_unknown()])?; js_array.insert(result)?; } @@ -240,31 +84,30 @@ impl YArray { | null | undefined" )] pub fn insert(&mut self, index: i64, value: MixedRefYType) -> Result<()> { - let mut this = self.array.write().unwrap(); match value { - MixedRefYType::A(array) => { - if let ManagedArray::YArray { array: yarray, doc } = this.deref_mut() { - let array = array.integrate_to_ytype(doc).map_err(anyhow::Error::from)?; - yarray.insert(index as u64, array).map_err(anyhow::Error::from) - } else { - Err(cannot_insert_nested_ytype()) - } - } - MixedRefYType::B(map) => this.insert(index as u64, map.map.clone()).map_err(anyhow::Error::from), - MixedRefYType::C(text) => this + MixedRefYType::A(array) => self + .array + .insert(index as u64, array.array.clone()) + .map_err(anyhow::Error::from), + MixedRefYType::B(map) => self + .array + .insert(index as u64, map.map.clone()) + .map_err(anyhow::Error::from), + MixedRefYType::C(text) => self + .array .insert(index as u64, text.text.clone()) .map_err(anyhow::Error::from), MixedRefYType::D(unknown) => match unknown.get_type() { Ok(value_type) => match value_type { ValueType::Undefined | ValueType::Null => { - this.insert(index as u64, Any::Null).map_err(anyhow::Error::from) + self.array.insert(index as u64, Any::Null).map_err(anyhow::Error::from) } ValueType::Boolean => match unknown.coerce_to_bool().and_then(|v| v.get_value()) { - Ok(boolean) => this.insert(index as u64, boolean).map_err(anyhow::Error::from), + Ok(boolean) => self.array.insert(index as u64, boolean).map_err(anyhow::Error::from), Err(e) => Err(anyhow::Error::new(e).context("Failed to coerce value to boolean")), }, ValueType::Number => match unknown.coerce_to_number().and_then(|v| v.get_double()) { - Ok(number) => this.insert(index as u64, number).map_err(anyhow::Error::from), + Ok(number) => self.array.insert(index as u64, number).map_err(anyhow::Error::from), Err(e) => Err(anyhow::Error::new(e).context("Failed to coerce value to number")), }, ValueType::String => { @@ -273,7 +116,7 @@ impl YArray { .and_then(|v| v.into_utf8()) .and_then(|s| s.as_str().map(|s| s.to_string())) { - Ok(string) => this.insert(index as u64, string).map_err(anyhow::Error::from), + Ok(string) => self.array.insert(index as u64, string).map_err(anyhow::Error::from), Err(e) => Err(anyhow::Error::new(e).context("Failed to coerce value to string")), } } @@ -282,7 +125,6 @@ impl YArray { .and_then(|o| o.get_array_length().map(|l| (o, l))) { Ok((object, length)) => { - drop(this); for i in 0..length { if let Ok(unknown) = object.get_element::(i) { self.insert(index + i as i64, MixedRefYType::from_unknown(unknown)?)?; @@ -321,8 +163,6 @@ impl YArray { #[napi] pub fn delete(&mut self, index: i64, len: Option) -> Result<()> { self.array - .write() - .unwrap() .remove(index as u64, len.unwrap_or(1) as u64) .map_err(anyhow::Error::from) } @@ -339,10 +179,8 @@ impl YArray { #[napi] pub fn to_array(&self, env: Env) -> Result { let mut js_array = env.create_array(0)?; - let array = self.array.read().unwrap(); - let doc = array.doc(); - for value in array.iter() { - js_array.insert(get_js_unknown_from_value(env, doc, value)?)?; + for value in self.array.iter() { + js_array.insert(get_js_unknown_from_value(env, value)?)?; } Ok(js_array) } @@ -350,10 +188,8 @@ impl YArray { #[napi(js_name = "toJSON")] pub fn to_json(&self, env: Env) -> Result { let mut js_array = env.create_array(0)?; - let array = self.array.read().unwrap(); - let doc = array.doc(); - for value in array.iter() { - js_array.insert(get_js_unknown_from_value(env, doc, value)?)?; + for value in self.array.iter() { + js_array.insert(get_js_unknown_from_value(env, value)?)?; } Ok(js_array) } diff --git a/y-octo-node/src/doc.rs b/y-octo-node/src/doc.rs index 35130c7..a6afb7d 100644 --- a/y-octo-node/src/doc.rs +++ b/y-octo-node/src/doc.rs @@ -54,7 +54,7 @@ impl YDoc { pub fn get_or_create_array(&self, key: String) -> Result { self.doc .get_or_create_array(key) - .map(|a| YArray::inner_new(a, &self.doc)) + .map(YArray::inner_new) .map_err(anyhow::Error::from) } @@ -70,7 +70,7 @@ impl YDoc { pub fn get_or_create_map(&self, key: String) -> Result { self.doc .get_or_create_map(key) - .map(|m| YMap::inner_new(m, &self.doc)) + .map(YMap::inner_new) .map_err(anyhow::Error::from) } @@ -78,7 +78,7 @@ impl YDoc { pub fn create_array(&self) -> Result { self.doc .create_array() - .map(|a| YArray::inner_new(a, &self.doc)) + .map(YArray::inner_new) .map_err(anyhow::Error::from) } @@ -93,7 +93,7 @@ impl YDoc { #[napi] pub fn create_map(&self, env: Env, entries: Option) -> Result { - let mut ymap = self.doc.create_map().map(|m| YMap::inner_new(m, &self.doc))?; + let mut ymap = self.doc.create_map().map(YMap::inner_new)?; if let Some(entries) = entries { for i in 0..entries.len() { if let Ok(Some(value)) = entries.get::(i) { diff --git a/y-octo-node/src/map.rs b/y-octo-node/src/map.rs index ae27901..d7bdcad 100644 --- a/y-octo-node/src/map.rs +++ b/y-octo-node/src/map.rs @@ -1,18 +1,17 @@ use napi::{bindgen_prelude::Array as JsArray, iterator::Generator, Env, JsFunction, JsObject, JsUnknown, ValueType}; -use y_octo::{Any, Doc, Map, Value}; +use y_octo::{Any, Map, Value}; use super::*; #[napi] pub struct YMap { pub(crate) map: Map, - doc: Doc, } #[napi] impl YMap { - pub(crate) fn inner_new(map: Map, doc: &Doc) -> Self { - Self { map, doc: doc.clone() } + pub(crate) fn inner_new(map: Map) -> Self { + Self { map } } #[napi(getter)] @@ -40,8 +39,8 @@ impl YMap { if let Some(value) = self.map.get(&key) { match value { Value::Any(any) => get_js_unknown_from_any(env, any).map(MixedYType::D), - Value::Array(array) => Ok(MixedYType::A(YArray::inner_new(array, &self.doc))), - Value::Map(map) => Ok(MixedYType::B(YMap::inner_new(map, &self.doc))), + Value::Array(array) => Ok(MixedYType::A(YArray::inner_new(array))), + Value::Map(map) => Ok(MixedYType::B(YMap::inner_new(map))), Value::Text(text) => Ok(MixedYType::C(YText::inner_new(text))), _ => env.get_null().map(|v| v.into_unknown()).map(MixedYType::D), } @@ -60,13 +59,12 @@ impl YMap { pub fn set(&mut self, env: Env, key: String, value: MixedRefYType) -> Result { match value { MixedRefYType::A(array) => { - let array = array.integrate_to_ytype(&self.doc).map_err(anyhow::Error::from)?; - self.map.insert(key, array.clone())?; - Ok(MixedYType::A(YArray::inner_new(array, &self.doc))) + self.map.insert(key, array.array.clone())?; + Ok(MixedYType::A(YArray::inner_new(array.array.clone()))) } MixedRefYType::B(map) => { self.map.insert(key, map.map.clone())?; - Ok(MixedYType::B(YMap::inner_new(map.map.clone(), &self.doc))) + Ok(MixedYType::B(YMap::inner_new(map.map.clone()))) } MixedRefYType::C(text) => { self.map.insert(key, text.text.clone())?; @@ -139,7 +137,7 @@ impl YMap { pub fn to_json(&self, env: Env) -> Result { let mut js_object = env.create_object()?; for (key, value) in self.map.iter() { - js_object.set(key, get_js_unknown_from_value(env, Some(&self.doc), value))?; + js_object.set(key, get_js_unknown_from_value(env, value))?; } Ok(js_object) } @@ -149,7 +147,6 @@ impl YMap { YMapEntriesIterator { entries: self.map.iter().map(|(k, v)| (k.to_owned(), v)).collect(), env, - doc: self.doc.clone(), current: 0, } } @@ -167,7 +164,6 @@ impl YMap { YMapValuesIterator { entries: self.map.iter().map(|(_, v)| v).collect(), env, - doc: self.doc.clone(), current: 0, } } @@ -189,7 +185,6 @@ impl YMap { pub struct YMapEntriesIterator { entries: Vec<(String, Value)>, env: Env, - doc: Doc, current: i64, } @@ -210,10 +205,7 @@ impl Generator for YMapEntriesIterator { let mut js_array = self.env.create_array(2).ok()?; js_array.set(0, string).ok()?; js_array - .set( - 1, - get_js_unknown_from_value(self.env, Some(&self.doc), value.clone()).ok()?, - ) + .set(1, get_js_unknown_from_value(self.env, value.clone()).ok()?) .ok()?; Some(js_array) } else { @@ -261,7 +253,6 @@ impl Generator for YMapKeyIterator { pub struct YMapValuesIterator { entries: Vec, env: Env, - doc: Doc, current: i64, } @@ -279,7 +270,7 @@ impl Generator for YMapValuesIterator { return None; } let ret = if let Some(value) = self.entries.get(current) { - get_js_unknown_from_value(self.env, Some(&self.doc), value.clone()).ok() + get_js_unknown_from_value(self.env, value.clone()).ok() } else { None }; diff --git a/y-octo-node/src/utils.rs b/y-octo-node/src/utils.rs index 4ae9613..ef1f4fc 100644 --- a/y-octo-node/src/utils.rs +++ b/y-octo-node/src/utils.rs @@ -1,5 +1,5 @@ use napi::{bindgen_prelude::Either4, Env, Error, JsObject, JsUnknown, Result, Status, ValueType}; -use y_octo::{AHashMap, Any, Doc, HashMapExt, Value}; +use y_octo::{AHashMap, Any, HashMapExt, Value}; use super::*; @@ -34,14 +34,14 @@ pub fn get_js_unknown_from_any(env: Env, any: Any) -> Result { } } -pub fn get_js_unknown_from_value(env: Env, doc: Option<&Doc>, value: Value) -> Result { +pub fn get_js_unknown_from_value(env: Env, value: Value) -> Result { match value { Value::Any(any) => get_js_unknown_from_any(env, any), - Value::Array(array) if doc.is_some() => env - .create_external(YArray::inner_new(array, doc.unwrap()), None) + Value::Array(array) => env + .create_external(YArray::inner_new(array), None) .map(|o| o.into_unknown()), - Value::Map(map) if doc.is_some() => env - .create_external(YMap::inner_new(map, doc.unwrap()), None) + Value::Map(map) => env + .create_external(YMap::inner_new(map), None) .map(|o| o.into_unknown()), Value::Text(text) => env .create_external(YText::inner_new(text), None) diff --git a/y-octo-node/yocto.d.ts b/y-octo-node/yocto.d.ts index 4f3ffbc..6eed194 100644 --- a/y-octo-node/yocto.d.ts +++ b/y-octo-node/yocto.d.ts @@ -15,7 +15,6 @@ export declare function applyUpdate(doc: Doc, update: Buffer): void export declare function mergeUpdates(updates: Array): Buffer export declare function isAbstractType(unknown: unknown): boolean export declare class YArray { - static from(array: JsArray): this get length(): number get isEmpty(): boolean get(index: number): T From 42102e39219abb9809bdcb697580ec90e93a2e4a Mon Sep 17 00:00:00 2001 From: DarkSky Date: Fri, 30 Aug 2024 14:54:35 +0800 Subject: [PATCH 33/75] feat: y protocol --- y-octo-node/src/lib.rs | 2 + y-octo-node/src/protocol.rs | 74 +++++++++++++++++++++++++++++++++++++ y-octo/src/lib.rs | 3 +- y-octo/src/protocol/mod.rs | 3 +- 4 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 y-octo-node/src/protocol.rs diff --git a/y-octo-node/src/lib.rs b/y-octo-node/src/lib.rs index c5f3016..f0294c9 100644 --- a/y-octo-node/src/lib.rs +++ b/y-octo-node/src/lib.rs @@ -6,6 +6,7 @@ mod awareness; mod doc; mod function; mod map; +mod protocol; mod text; mod types; mod utils; @@ -17,6 +18,7 @@ pub use function::*; pub use map::*; pub use text::*; pub use types::*; +pub use protocol::*; use utils::{ get_any_from_js_object, get_any_from_js_unknown, get_js_unknown_from_any, get_js_unknown_from_value, MixedRefYType, MixedYType, diff --git a/y-octo-node/src/protocol.rs b/y-octo-node/src/protocol.rs new file mode 100644 index 0000000..1738808 --- /dev/null +++ b/y-octo-node/src/protocol.rs @@ -0,0 +1,74 @@ +use super::*; +use napi::bindgen_prelude::Buffer; +use y_octo::{ + read_doc_message, write_doc_message, CrdtRead, CrdtWrite, Doc, DocMessage, RawDecoder, RawEncoder, StateVector, +}; + +#[napi(js_name = "Protocol")] +pub struct YProtocol { + pub(crate) doc: Doc, +} + +#[napi] +impl YProtocol { + #[napi(constructor)] + pub fn new(doc: &YDoc) -> Self { + Self { doc: doc.doc.clone() } + } + + #[napi] + pub fn encode_sync_step(&self, step: u8, buffer: Option) -> Result { + match step { + 1 => { + let sv = self.doc.get_state_vector(); + let mut encoder = RawEncoder::default(); + sv.write(&mut encoder)?; + + let mut buffer = vec![]; + write_doc_message(&mut buffer, &DocMessage::Step1(encoder.into_inner()))?; + Ok(buffer.into()) + } + 2 => { + if let Some(sv) = buffer { + let sv = StateVector::read(&mut RawDecoder::new(&sv))?; + let update = self.doc.encode_state_as_update_v1(&sv)?; + let mut buffer = vec![]; + write_doc_message(&mut buffer, &DocMessage::Step2(update)).unwrap(); + Ok(buffer.into()) + } else { + Err(anyhow::Error::msg("State vector is required for sync step 2.").into()) + } + } + 3 => { + let update = if let Some(update) = buffer { + update.to_vec() + } else { + self.doc.encode_update_v1()? + }; + let mut buffer = vec![]; + write_doc_message(&mut buffer, &DocMessage::Update(update)).unwrap(); + Ok(buffer.into()) + } + _ => Err(anyhow::Error::msg("Invalid sync step. Must be 1, 2, or 3.").into()), + } + } + + #[napi] + pub fn apply_sync_step(&mut self, buffer: Buffer) -> Result> { + match read_doc_message(buffer.as_ref()) { + Ok((tail, message)) => { + if !tail.is_empty() { + return Err(anyhow::Error::msg("Invalid sync message buffer.").into()); + } + Ok(match message { + DocMessage::Step1(sv) => Some(self.encode_sync_step(2, Some(sv.into()))?), + DocMessage::Step2(binary) | DocMessage::Update(binary) => { + self.doc.apply_update_from_binary_v1(binary)?; + None + } + }) + } + Err(e) => Err(anyhow::Error::msg(format!("Invalid sync message buffer: {}", e.to_string())).into()), + } + } +} diff --git a/y-octo/src/lib.rs b/y-octo/src/lib.rs index 443e922..f242d02 100644 --- a/y-octo/src/lib.rs +++ b/y-octo/src/lib.rs @@ -15,7 +15,8 @@ pub(crate) use doc::{Content, Item}; use log::{debug, warn}; use nom::IResult; pub use protocol::{ - read_sync_message, write_sync_message, AwarenessState, AwarenessStates, DocMessage, SyncMessage, SyncMessageScanner, + read_doc_message, read_sync_message, write_doc_message, write_sync_message, AwarenessState, AwarenessStates, + DocMessage, SyncMessage, SyncMessageScanner, }; use thiserror::Error; diff --git a/y-octo/src/protocol/mod.rs b/y-octo/src/protocol/mod.rs index 51cd0c6..3b8e5bb 100644 --- a/y-octo/src/protocol/mod.rs +++ b/y-octo/src/protocol/mod.rs @@ -10,8 +10,7 @@ use std::{ use awareness::{read_awareness, write_awareness}; pub use awareness::{AwarenessState, AwarenessStates}; -pub use doc::DocMessage; -use doc::{read_doc_message, write_doc_message}; +pub use doc::{read_doc_message, write_doc_message, DocMessage}; use log::debug; use nom::{ error::{Error, ErrorKind}, From fbbb5b1193e868529e705139028c8f15771f227d Mon Sep 17 00:00:00 2001 From: DarkSky Date: Fri, 30 Aug 2024 16:54:57 +0800 Subject: [PATCH 34/75] feat: state differ --- y-octo-node/src/doc.rs | 35 ++++++++++++++++++----------------- y-octo/Cargo.toml | 2 ++ y-octo/src/doc/publisher.rs | 5 ++++- 3 files changed, 24 insertions(+), 18 deletions(-) diff --git a/y-octo-node/src/doc.rs b/y-octo-node/src/doc.rs index a6afb7d..084f7b7 100644 --- a/y-octo-node/src/doc.rs +++ b/y-octo-node/src/doc.rs @@ -120,17 +120,25 @@ impl YDoc { } #[napi] - pub fn encode_state_as_update_v1(&self, state: Option) -> Result { - let result = match state { - Some(state) => { - let mut decoder = RawDecoder::new(state.as_ref()); - let state = StateVector::read(&mut decoder)?; - self.doc.encode_state_as_update_v1(&state) - } - None => self.doc.encode_update_v1(), - }; + pub fn diff(&self, sv: Option) -> Result> { + if let Some(sv) = sv { + let mut decoder = RawDecoder::new(sv.as_ref()); + let state = StateVector::read(&mut decoder)?; + let update = self.doc.encode_state_as_update_v1(&state)?; + Ok(Some(update.into())) + } else { + Ok(None) + } + } - result.map(|v| v.into()).map_err(anyhow::Error::from) + #[napi] + pub fn encode_state_as_update_v1(&self, state: Option) -> Result { + if let Some(buffer) = self.diff(state)? { + Ok(buffer) + } else { + let buffer = self.doc.encode_update_v1()?; + Ok(buffer.into()) + } } #[napi] @@ -155,13 +163,6 @@ impl YDoc { self.doc.unsubscribe_all(); Ok(()) } - - #[napi] - pub fn transact(&mut self, callback: JsFunction) -> Result<()> { - callback.call_without_args(None)?; - - Ok(()) - } } #[cfg(test)] diff --git a/y-octo/Cargo.toml b/y-octo/Cargo.toml index eb4284e..eb52e7f 100644 --- a/y-octo/Cargo.toml +++ b/y-octo/Cargo.toml @@ -38,8 +38,10 @@ thiserror = "1.0" [features] bench = [] debug = [] +default = ["subscribe"] large_refs = [] serde_json = [] +subscribe = [] [target.'cfg(fuzzing)'.dependencies] arbitrary = { version = "1.3", features = ["derive"] } diff --git a/y-octo/src/doc/publisher.rs b/y-octo/src/doc/publisher.rs index ff7eba5..df7e111 100644 --- a/y-octo/src/doc/publisher.rs +++ b/y-octo/src/doc/publisher.rs @@ -34,7 +34,10 @@ impl DocPublisher { observing: Arc::new(AtomicBool::new(false)), }; - if cfg!(not(any(feature = "bench", fuzzing, loom, miri))) { + if cfg!(all( + feature = "subscribe", + not(any(feature = "bench", fuzzing, loom, miri)) + )) { publisher.start(); } From 4813061e1a7b01c8dd5f30575c35fa7023f41f92 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Fri, 30 Aug 2024 17:55:48 +0800 Subject: [PATCH 35/75] feat: update event in js level --- y-octo-node/index.ts | 96 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 85 insertions(+), 11 deletions(-) diff --git a/y-octo-node/index.ts b/y-octo-node/index.ts index 6c4e4e5..10a42a1 100644 --- a/y-octo-node/index.ts +++ b/y-octo-node/index.ts @@ -17,24 +17,89 @@ type ListItem = | Record | null; -export const Doc = Y.Doc; +export class Doc extends Y.Doc { + private cachedArray: globalThis.Map = new globalThis.Map(); + private cachedMap: globalThis.Map = new globalThis.Map(); + private subscribers: Set<(result: Uint8Array, origin?: unknown) => void> = + new Set(); + private lastState: Buffer | null = null; + + getArray(key: string): Array { + if (this.cachedArray.has(key)) { + return this.cachedArray.get(key)!; + } + const yarray = new Array([], this, this.getOrCreateArray(key)); + this.cachedArray.set(key, yarray); + return yarray; + } + + getMap(key: string): Map { + if (this.cachedMap.has(key)) { + return this.cachedMap.get(key)!; + } + const ymap = new Map({}, this, this.getOrCreateMap(key)); + this.cachedMap.set(key, ymap); + return ymap; + } + + getText(key: string): Text { + return this.getOrCreateText(key); + } + + triggerDiff(origin?: unknown): void { + if (this.lastState) { + const diff = this.diff(this.lastState); + if (diff && !this.lastState.equals(diff)) { + this.lastState = diff; + } else { + return; + } + } else { + this.lastState = this.encodeStateAsUpdateV1(); + } + + if (this.lastState?.length) { + this.subscribers.forEach((callback) => + callback(new Uint8Array(this.lastState!), origin || this), + ); + } + } + + transact(callback: (...args: any[]) => any, origin?: unknown): void { + try { + callback(); + } finally { + this.triggerDiff(origin); + } + } + + override onUpdate( + callback: (result: Uint8Array, origin?: unknown) => void, + ): void { + this.subscribers.add(callback); + } + + override offUpdate(): void { + this.subscribers.clear(); + } +} export class Array { - private ytype?: { doc: Y.Doc; array: Y.YArray }; + private ytype?: { doc: Doc; array: Y.YArray }; private preliminary: any[] = []; static from(items: T[]): Array { return new Array(items); } - constructor(items: ArrayType[], ydoc?: Y.YDoc) { + constructor(items: ArrayType[], ydoc?: Doc, yarray?: Y.YArray) { this.preliminary = items; - if (ydoc) this.integrate(ydoc); + if (ydoc) this.integrate(ydoc, yarray); } - integrate(ydoc: Y.YDoc): Y.YArray { + integrate(ydoc: Doc, yarray?: Y.YArray): Y.YArray { if (!this.ytype) { - this.ytype = { doc: ydoc, array: ydoc.createArray() }; + this.ytype = { doc: ydoc, array: yarray || ydoc.createArray() }; for (const item of this.preliminary) { if (item instanceof Array) { this.ytype.array.push(item.integrate(ydoc)); @@ -43,6 +108,7 @@ export class Array { } } this.preliminary = []; + this.ytype.doc.triggerDiff(); } return this.ytype.array; } @@ -80,6 +146,7 @@ export class Array { } else { this.ytype.array.insert(index, value); } + this.ytype.doc.triggerDiff(); } else { this.preliminary.splice(index, 0, value); } @@ -88,6 +155,7 @@ export class Array { push(value?: ListItem): void { if (this.ytype) { this.ytype.array.push(value); + this.ytype.doc.triggerDiff(); } else { this.preliminary.push(value); } @@ -96,6 +164,7 @@ export class Array { unshift(value?: ListItem): void { if (this.ytype) { this.ytype.array.unshift(value); + this.ytype.doc.triggerDiff(); } else { this.preliminary.unshift(value); } @@ -104,6 +173,7 @@ export class Array { delete(index: number, len?: number): void { if (this.ytype) { this.ytype.array.delete(index, len); + this.ytype.doc.triggerDiff(); } else { this.preliminary.splice(index, len); } @@ -141,17 +211,17 @@ export class Array { } export class Map { - private ytype?: { doc: Y.Doc; map: Y.YMap }; + private ytype?: { doc: Doc; map: Y.YMap }; private preliminary: Record = {}; - constructor(items: Record, ydoc?: Y.YDoc) { + constructor(items: Record, ydoc?: Doc, ymap?: Y.YMap) { this.preliminary = items; - if (ydoc) this.integrate(ydoc); + if (ydoc) this.integrate(ydoc, ymap); } - integrate(ydoc: Y.YDoc): Y.YMap { + integrate(ydoc: Doc, ymap?: Y.YMap): Y.YMap { if (!this.ytype) { - this.ytype = { doc: ydoc, map: ydoc.createMap() }; + this.ytype = { doc: ydoc, map: ymap || ydoc.createMap() }; for (const [key, val] of Object.entries(this.preliminary)) { if (val instanceof Array) { this.ytype.map.set(key, val.integrate(ydoc)); @@ -160,6 +230,7 @@ export class Map { } } this.preliminary = {}; + this.ytype.doc.triggerDiff(); } return this.ytype.map; } @@ -193,6 +264,7 @@ export class Map { } else { this.ytype.map.set(key, value); } + this.ytype.doc.triggerDiff(); } else { this.preliminary[key] = value; } @@ -201,6 +273,7 @@ export class Map { delete(key: string): void { if (this.ytype) { this.ytype.map.delete(key); + this.ytype.doc.triggerDiff(); } else { delete this.preliminary[key]; } @@ -209,6 +282,7 @@ export class Map { clear(): void { if (this.ytype) { this.ytype.map.clear(); + this.ytype.doc.triggerDiff(); } else { this.preliminary = {}; } From 257b2db38b4ba774932f44b40500161c2ab68be4 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Mon, 2 Sep 2024 14:57:21 +0800 Subject: [PATCH 36/75] feat: replace yprotocol --- y-octo-node/Cargo.toml | 10 ++- y-octo-node/package.json | 5 +- y-octo-node/tests/yjs/testHelper.ts | 135 ++++++++++------------------ y-octo-node/yocto.d.ts | 8 +- y-octo-node/yocto.js | 3 +- yarn.lock | 14 +-- 6 files changed, 65 insertions(+), 110 deletions(-) diff --git a/y-octo-node/Cargo.toml b/y-octo-node/Cargo.toml index e1b2d72..a6ae44c 100644 --- a/y-octo-node/Cargo.toml +++ b/y-octo-node/Cargo.toml @@ -11,11 +11,13 @@ version = "0.0.1" crate-type = ["cdylib"] [dependencies] -anyhow = "1" -napi = { version = "2", features = ["anyhow", "napi4", "serde-json"] } +anyhow = "1" +napi = { version = "2", features = ["anyhow", "napi4", "serde-json"] } napi-derive = "2" -serde_json = "1.0" -y-octo = { workspace = true, features = ["large_refs"] } +serde_json = "1.0" +y-octo = { workspace = true, features = [ + "large_refs", +], default-features = false } [build-dependencies] napi-build = "2" diff --git a/y-octo-node/package.json b/y-octo-node/package.json index bff70f4..3ba5b80 100644 --- a/y-octo-node/package.json +++ b/y-octo-node/package.json @@ -28,7 +28,6 @@ "prompts": "^2.4.2", "tsx": "^4.16.2", "typescript": "^5.1.6", - "y-protocols": "^1.0.6", "yjs": "^13.6.18" }, "engines": { @@ -39,7 +38,7 @@ "build": "napi build --platform --release --no-const-enum --dts yocto.d.ts --js yocto.js", "build:debug": "napi build --platform --no-const-enum --dts yocto.d.ts --js yocto.js", "universal": "napi universal", - "test": "ava --concurrency 1 --serial", + "test": "ava --concurrency 1 --serial --timeout 1s", "test:watch": "yarn exec tsx ./scripts/run-test.mts all --watch", "test:coverage": "NODE_OPTIONS=\"--import tsx\" c8 node ./scripts/run-test.mts all", "version": "napi version" @@ -71,7 +70,7 @@ "--es-module-specifier-resolution=node" ], "files": [ - "tests/**/*.spec.ts" + "tests/**/y-array.spec.ts" ], "environmentVariables": { "TS_NODE_PROJECT": "./tests/tsconfig.json", diff --git a/y-octo-node/tests/yjs/testHelper.ts b/y-octo-node/tests/yjs/testHelper.ts index 225da7d..dd836a9 100644 --- a/y-octo-node/tests/yjs/testHelper.ts +++ b/y-octo-node/tests/yjs/testHelper.ts @@ -1,8 +1,5 @@ import assert, { deepEqual } from "node:assert"; 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 object from "lib0/object"; import * as map from "lib0/map"; import * as Y from "../../index"; @@ -28,59 +25,28 @@ const broadcastMessage = (y: TestYOctoInstance, m: Uint8Array) => { } }; -export let useV2 = false; - -export const encV1 = { - encodeStateAsUpdate: Y.encodeStateAsUpdate, - mergeUpdates: Y.mergeUpdates, - applyUpdate: Y.applyUpdate, - // logUpdate: Y.logUpdate, - // updateEventName: /** @type {'update'} */ "update", - // diffUpdate: Y.diffUpdate, -}; - -// export const encV2 = { -// encodeStateAsUpdate: Y.encodeStateAsUpdateV2, -// mergeUpdates: Y.mergeUpdatesV2, -// applyUpdate: Y.applyUpdateV2, -// logUpdate: Y.logUpdateV2, -// updateEventName: /** @type {'updateV2'} */ "updateV2", -// diffUpdate: Y.diffUpdateV2, -// }; - -export let enc = encV1; - -const useV1Encoding = () => { - useV2 = false; - enc = encV1; -}; - -const useV2Encoding = () => { - console.error( - "sync protocol doesnt support v2 protocol yet, fallback to v1 encoding", - ); // @Todo - useV2 = false; - enc = encV1; -}; - export class TestYOctoInstance extends Y.Doc { + protocol: Y.Protocol; updates: Uint8Array[]; receiving: Map; tc: TestConnector; constructor(testConnector: TestConnector, clientID: number) { super(clientID); // overwriting clientID + this.protocol = new Y.Protocol(this); this.tc = testConnector; this.receiving = new Map(); testConnector.allConns.add(this); this.updates = []; // set up observe on local model - this.onUpdate((update) => { - // if (origin !== testConnector) { - // const encoder = encoding.createEncoder(); - // syncProtocol.writeUpdate(encoder, update); - // broadcastMessage(this, encoding.toUint8Array(encoder)); - // } + this.onUpdate((update, origin) => { + if (origin !== testConnector) { + console.error("broadcasting update"); + broadcastMessage( + this, + this.protocol.encodeSyncStep(3, Buffer.from(update)), + ); + } this.updates.push(update); }); this.connect(); @@ -100,19 +66,17 @@ export class TestYOctoInstance extends Y.Doc { * Also initiate sync with all clients. */ connect() { - return; if (!this.tc.onlineConns.has(this)) { this.tc.onlineConns.add(this); - const encoder = encoding.createEncoder(); - syncProtocol.writeSyncStep1(encoder, this); // publish SyncStep1 - broadcastMessage(this, encoding.toUint8Array(encoder)); + broadcastMessage(this, this.protocol.encodeSyncStep(1)); this.tc.onlineConns.forEach((remoteYInstance) => { if (remoteYInstance !== this) { // remote instance sends instance to this instance - const encoder = encoding.createEncoder(); - syncProtocol.writeSyncStep1(encoder, remoteYInstance); - this._receive(encoding.toUint8Array(encoder), remoteYInstance); + this._receive( + remoteYInstance.protocol.encodeSyncStep(1), + remoteYInstance, + ); } }); } @@ -165,6 +129,7 @@ export class TestConnector { gen, Array.from(receiver.receiving), ); + const m = messages.shift(); if (messages.length === 0) { receiver.receiving.delete(sender); @@ -172,18 +137,12 @@ export class TestConnector { if (m === undefined) { return this.flushRandomMessage(); } - const encoder = encoding.createEncoder(); - // console.log('receive (' + sender.userID + '->' + receiver.userID + '):\n', syncProtocol.stringifySyncMessage(decoding.createDecoder(m), receiver)) + // do not publish data created when this function is executed (could be ss2 or update message) - syncProtocol.readSyncMessage( - decoding.createDecoder(m), - encoder, - receiver, - receiver.tc, - ); - if (encoding.length(encoder) > 0) { + const update = receiver.protocol.applySyncStep(Buffer.from(m)); + if (update) { // send reply message - sender._receive(encoding.toUint8Array(encoder), receiver); + sender._receive(update, receiver); } return true; } @@ -266,13 +225,6 @@ export const init = ( { users = 5 }: { users?: number } = {}, initTestObject?: any, ): InitResult => { - // choose an encoding approach at random - if (prng.bool(gen)) { - useV2Encoding(); - } else { - useV1Encoding(); - } - // @ts-expect-error expect const result: InitResult = { users: [], @@ -282,14 +234,13 @@ export const init = ( const y = result.testConnector.createY(i); y.clientId = i; result.users.push(y); - result["array" + i] = y.getOrCreateArray("array"); - result["map" + i] = y.getOrCreateMap("map"); + result["array" + i] = y.getArray("array"); + result["map" + i] = y.getMap("map"); // result["xml" + i] = y.get("xml", Y.XmlElement); - result["text" + i] = y.getOrCreateText("text"); + result["text" + i] = y.getText("text"); } result.testConnector.syncAll(); result.testObjects = result.users.map(initTestObject || (() => null)); - useV1Encoding(); return result; }; @@ -305,16 +256,19 @@ export const compare = (users: TestYOctoInstance[]) => { while (users[0].tc.flushAllMessages()) {} // eslint-disable-line // For each document, merge all received document updates with Y.mergeUpdates and create a new document which will be added to the list of "users" // This ensures that mergeUpdates works correctly - const mergedDocs = users.map((user: { updates: any }) => { + const mergedDocs = users.map((user: { updates: any }, i) => { const ydoc = new Y.Doc(); - enc.applyUpdate(ydoc, enc.mergeUpdates(user.updates)); + console.error( + `Merging user ${i}'s updates: ${user.updates + .map((u: any) => u.length) + .join(", ")}`, + ); + Y.applyUpdate(ydoc, Y.mergeUpdates(user.updates)); return ydoc; }); users.push(...mergedDocs); - const userArrayValues = users.map((u) => - u.getOrCreateArray("array").toJSON(), - ); - const userMapValues = users.map((u) => u.getOrCreateMap("map").toJson()); + const userArrayValues = users.map((u) => u.getArray("array").toJSON()); + const userMapValues = users.map((u) => u.getMap("map").toJson()); // const userXmlValues = users.map( // (u: { // get: ( @@ -323,34 +277,39 @@ export const compare = (users: TestYOctoInstance[]) => { // ) => { (): any; new (): any; toString: { (): any; new (): any } }; // }) => u.get("xml", Y.XmlElement).toString(), // ); - // const userTextValues = users.map((u) => u.getOrCreateText("text").toDelta()); + // const userTextValues = users.map((u) => u.getText("text").toDelta()); // for (const u of users) { // t.assert(u.store.pendingDs === null); // t.assert(u.store.pendingStructs === null); // } // Test Array iterator deepEqual( - users[0].getOrCreateArray("array").toArray(), - Array.from(users[0].getOrCreateArray("array").iter()), + users[0].getArray("array").toArray(), + Array.from(users[0].getArray("array").iter()), ); // Test Map iterator - const ymapkeys: any[] = Array.from(users[0].getOrCreateMap("map").keys()); + const ymapkeys: any[] = Array.from(users[0].getMap("map").keys()); assert(ymapkeys.length === Object.keys(userMapValues[0]).length); ymapkeys.forEach((key) => assert(object.hasProperty(userMapValues[0], key))); const mapRes: Record = {}; - for (const [k, v] of users[0].getOrCreateMap("map").entries()) { + for (const [k, v] of users[0].getMap("map").entries()) { mapRes[k] = Y.isAbstractType(v) ? v.toJSON() : v; } deepEqual(userMapValues[0], mapRes); // Compare all users for (let i = 0; i < users.length - 1; i++) { + deepEqual(userArrayValues[i].length, users[i].getArray("array").length); + deepEqual( + userArrayValues[i], + userArrayValues[i + 1], + `array${i} !== array${i + 1}`, + ); deepEqual( - userArrayValues[i].length, - users[i].getOrCreateArray("array").length, + userMapValues[i], + userMapValues[i + 1], + `map${i} !== map${i + 1}`, ); - deepEqual(userArrayValues[i], userArrayValues[i + 1]); - deepEqual(userMapValues[i], userMapValues[i + 1]); // deepEqual(userXmlValues[i], userXmlValues[i + 1]); // deepEqual( // userTextValues[i] @@ -359,7 +318,7 @@ export const compare = (users: TestYOctoInstance[]) => { // typeof a.insert === "string" ? a.insert : " ", // ) // .join("").length, - // users[i].getOrCreateText("text").length, + // users[i].getText("text").length, // ); // deepEqual( // userTextValues[i], diff --git a/y-octo-node/yocto.d.ts b/y-octo-node/yocto.d.ts index 6eed194..290841d 100644 --- a/y-octo-node/yocto.d.ts +++ b/y-octo-node/yocto.d.ts @@ -54,11 +54,11 @@ export declare class Doc { createText(text?: string | undefined | null): YText createMap(entries?: JsArray | undefined | null): YMap applyUpdate(update: Buffer): void + diff(sv?: Buffer | undefined | null): Buffer | null encodeStateAsUpdateV1(state?: Buffer | undefined | null): Buffer gc(): void onUpdate(callback: (result: Uint8Array) => void): void offUpdate(): void - transact(callback: (...args: any[]) => any): void } export declare class YMap { get length(): number @@ -85,6 +85,12 @@ export declare class YMapKeyIterator { export declare class YMapValuesIterator { [Symbol.iterator](): Iterator } +export type YProtocol = Protocol +export declare class Protocol { + constructor(doc: Doc) + encodeSyncStep(step: number, buffer?: Buffer | undefined | null): Buffer + applySyncStep(buffer: Buffer): Buffer | null +} export declare class YText { constructor() get len(): number diff --git a/y-octo-node/yocto.js b/y-octo-node/yocto.js index 9c6ba63..aa54186 100644 --- a/y-octo-node/yocto.js +++ b/y-octo-node/yocto.js @@ -310,7 +310,7 @@ if (!nativeBinding) { throw new Error(`Failed to load native binding`) } -const { YArray, YArrayIterator, Awareness, Doc, encodeStateAsUpdate, encodeStateVector, compareStructStores, compareIds, createDeleteSetFromStructStore, equalDeleteSets, snapshot, encodeSnapshot, applyUpdate, mergeUpdates, isAbstractType, YMap, YMapEntriesIterator, YMapKeyIterator, YMapValuesIterator, YText, Id, Store, DeleteSet, YSnapshot } = nativeBinding +const { YArray, YArrayIterator, Awareness, Doc, encodeStateAsUpdate, encodeStateVector, compareStructStores, compareIds, createDeleteSetFromStructStore, equalDeleteSets, snapshot, encodeSnapshot, applyUpdate, mergeUpdates, isAbstractType, YMap, YMapEntriesIterator, YMapKeyIterator, YMapValuesIterator, Protocol, YText, Id, Store, DeleteSet, YSnapshot } = nativeBinding module.exports.YArray = YArray module.exports.YArrayIterator = YArrayIterator @@ -331,6 +331,7 @@ module.exports.YMap = YMap module.exports.YMapEntriesIterator = YMapEntriesIterator module.exports.YMapKeyIterator = YMapKeyIterator module.exports.YMapValuesIterator = YMapValuesIterator +module.exports.Protocol = Protocol module.exports.YText = YText module.exports.Id = Id module.exports.Store = Store diff --git a/yarn.lock b/yarn.lock index 05a1cfc..a2d6b83 100644 --- a/yarn.lock +++ b/yarn.lock @@ -414,7 +414,6 @@ __metadata: prompts: ^2.4.2 tsx: ^4.16.2 typescript: ^5.1.6 - y-protocols: ^1.0.6 yjs: ^13.6.18 languageName: unknown linkType: soft @@ -1906,7 +1905,7 @@ __metadata: languageName: node linkType: hard -"lib0@npm:^0.2.85, lib0@npm:^0.2.86": +"lib0@npm:^0.2.86": version: 0.2.94 resolution: "lib0@npm:0.2.94" dependencies: @@ -3445,17 +3444,6 @@ __metadata: languageName: node linkType: hard -"y-protocols@npm:^1.0.6": - version: 1.0.6 - resolution: "y-protocols@npm:1.0.6" - dependencies: - lib0: ^0.2.85 - peerDependencies: - yjs: ^13.0.0 - checksum: 4b57c8811befcf2e45c3d47830005f8a33e626c734f78a42fe8a4fa3caad2233ba85a7c8bceefbd52ffc40130d3f3faee664fd0d1c324ff1fa8817a056ccdc1c - languageName: node - linkType: hard - "y18n@npm:^5.0.5": version: 5.0.8 resolution: "y18n@npm:5.0.8" From 711da9293452781c257486aa6715b23e9f38f24e Mon Sep 17 00:00:00 2001 From: DarkSky Date: Mon, 2 Sep 2024 16:02:25 +0800 Subject: [PATCH 37/75] chore: fix generate typing --- y-octo-node/src/array.rs | 4 ++-- y-octo-node/src/awareness.rs | 6 +----- y-octo-node/src/doc.rs | 2 +- y-octo-node/src/text.rs | 4 ++-- y-octo-node/yocto.d.ts | 12 ++++++------ 5 files changed, 12 insertions(+), 16 deletions(-) diff --git a/y-octo-node/src/array.rs b/y-octo-node/src/array.rs index 3d2fd87..31bd2c7 100644 --- a/y-octo-node/src/array.rs +++ b/y-octo-node/src/array.rs @@ -176,7 +176,7 @@ impl YArray { } } - #[napi] + #[napi(ts_generic_types = "T = unknown", ts_return_type = "Array")] pub fn to_array(&self, env: Env) -> Result { let mut js_array = env.create_array(0)?; for value in self.array.iter() { @@ -185,7 +185,7 @@ impl YArray { Ok(js_array) } - #[napi(js_name = "toJSON")] + #[napi(js_name = "toJSON", ts_generic_types = "T = unknown", ts_return_type = "Array")] pub fn to_json(&self, env: Env) -> Result { let mut js_array = env.create_array(0)?; for value in self.array.iter() { diff --git a/y-octo-node/src/awareness.rs b/y-octo-node/src/awareness.rs index 5418817..af4108c 100644 --- a/y-octo-node/src/awareness.rs +++ b/y-octo-node/src/awareness.rs @@ -25,11 +25,7 @@ impl YAwareness { self.awareness.local_id() as i64 } - #[napi( - getter, - ts_generic_types = "T = Record", - ts_return_type = "Record" - )] + #[napi(getter, ts_return_type = "Record")] pub fn states(&self, env: Env) -> Result { let mut object = env.create_object()?; for (k, v) in self.awareness.get_states() { diff --git a/y-octo-node/src/doc.rs b/y-octo-node/src/doc.rs index 084f7b7..16d2a79 100644 --- a/y-octo-node/src/doc.rs +++ b/y-octo-node/src/doc.rs @@ -91,7 +91,7 @@ impl YDoc { Ok(ytext) } - #[napi] + #[napi(ts_args_type = "entries?: Iterator<[string,any]>")] pub fn create_map(&self, env: Env, entries: Option) -> Result { let mut ymap = self.doc.create_map().map(YMap::inner_new)?; if let Some(entries) = entries { diff --git a/y-octo-node/src/text.rs b/y-octo-node/src/text.rs index fa60744..26da57f 100644 --- a/y-octo-node/src/text.rs +++ b/y-octo-node/src/text.rs @@ -45,12 +45,12 @@ impl YText { self.text.len() as i64 } - #[napi] + #[napi(ts_args_type = "delta: any[]")] pub fn apply_delta(&mut self, env: Env, _delta: JsArray) -> Result<()> { unimplemented!() } - #[napi] + #[napi(ts_return_type = "any[]")] pub fn to_delta(&self, env: Env) -> Result { unimplemented!() } diff --git a/y-octo-node/yocto.d.ts b/y-octo-node/yocto.d.ts index 290841d..16e9cb6 100644 --- a/y-octo-node/yocto.d.ts +++ b/y-octo-node/yocto.d.ts @@ -25,8 +25,8 @@ export declare class YArray { unshift(value: YArray | YMap | YText | boolean | number | string | Record | null | undefined): void delete(index: number, len?: number | undefined | null): void iter(): YArrayIterator - toArray(): JsArray - toJSON(): JsArray + toArray(): Array + toJSON(): Array observe(callback: (...args: any[]) => any): void observeDeep(callback: (...args: any[]) => any): void } @@ -37,7 +37,7 @@ export type YAwareness = Awareness export declare class Awareness { constructor(clientId?: number | undefined | null) get clientId(): number - get states>(): Record + get states(): Record } export type YDoc = Doc export declare class Doc { @@ -52,7 +52,7 @@ export declare class Doc { getOrCreateMap(key: string): YMap createArray(): YArray createText(text?: string | undefined | null): YText - createMap(entries?: JsArray | undefined | null): YMap + createMap(entries?: Iterator<[string,any]>): YMap applyUpdate(update: Buffer): void diff(sv?: Buffer | undefined | null): Buffer | null encodeStateAsUpdateV1(state?: Buffer | undefined | null): Buffer @@ -98,8 +98,8 @@ export declare class YText { insert(index: number, str: string): void delete(index: number, len: number): void get length(): number - applyDelta(delta: JsArray): void - toDelta(): JsArray + applyDelta(delta: any[]): void + toDelta(): any[] toString(): string observe(callback: (...args: any[]) => any): void observeDeep(callback: (...args: any[]) => any): void From 4ca680341e1d8263ec4c78f9c33f68db6a7660b3 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Mon, 2 Sep 2024 17:34:24 +0800 Subject: [PATCH 38/75] feat: rename protocol --- y-octo-node/src/protocol.rs | 2 +- y-octo-node/y-protocol.d.ts | 126 ------------------------------------ y-octo-node/yocto.d.ts | 3 +- y-octo-node/yocto.js | 4 +- 4 files changed, 4 insertions(+), 131 deletions(-) delete mode 100644 y-octo-node/y-protocol.d.ts diff --git a/y-octo-node/src/protocol.rs b/y-octo-node/src/protocol.rs index 1738808..1db4cd1 100644 --- a/y-octo-node/src/protocol.rs +++ b/y-octo-node/src/protocol.rs @@ -4,7 +4,7 @@ use y_octo::{ read_doc_message, write_doc_message, CrdtRead, CrdtWrite, Doc, DocMessage, RawDecoder, RawEncoder, StateVector, }; -#[napi(js_name = "Protocol")] +#[napi] pub struct YProtocol { pub(crate) doc: Doc, } diff --git a/y-octo-node/y-protocol.d.ts b/y-octo-node/y-protocol.d.ts deleted file mode 100644 index 3e6eba1..0000000 --- a/y-octo-node/y-protocol.d.ts +++ /dev/null @@ -1,126 +0,0 @@ -import * as encoding from "lib0/encoding"; -import * as decoding from "lib0/decoding"; -import { Observable } from "lib0/observable"; -import * as Y from "yjs"; - -export type AwarenessState = Record; -export type AwarenessStates = Map; - -export type MetaClientState = { - clock: number; - /** - * unix timestamp - */ - lastUpdated: number; -}; - -/** - * The Awareness class implements a simple shared state protocol that can be used for non-persistent data like awareness information - * (cursor, username, status, ..). Each client can update its own local state and listen to state changes of - * remote clients. Every client may set a state of a remote peer to `null` to mark the client as offline. - * - * Each client is identified by a unique client id (something we borrow from `doc.clientID`). A client can override - * its own state by propagating a message with an increasing timestamp (`clock`). If such a message is received, it is - * applied if the known state of that client is older than the new state (`clock < newClock`). If a client thinks that - * a remote client is offline, it may propagate a message with - * `{ clock: currentClientClock, state: null, client: remoteClient }`. If such a - * message is received, and the known clock of that client equals the received clock, it will override the state with `null`. - * - * Before a client disconnects, it should propagate a `null` state with an updated clock. - * - * Awareness states must be updated every 30 seconds. Otherwise the Awareness instance will delete the client state. - */ -export class Awareness extends Observable { - constructor(doc: Y.Doc); - doc: Y.Doc; - clientID: number; - /** - * Maps from client id to client state - */ - states: AwarenessStates; - meta: Map; - _checkInterval: any; - getLocalState(): AwarenessState | null; - setLocalState(state: AwarenessState | null): void; - setLocalStateField(field: string, value: any): void; - getStates(): AwarenessStates; -} - -export function removeAwarenessStates( - awareness: Awareness, - clients: Array, - origin: any, -): void; -export function encodeAwarenessUpdate( - awareness: Awareness, - clients: Array, - states?: AwarenessStates, -): Uint8Array; -export function modifyAwarenessUpdate( - update: Uint8Array, - modify: (arg0: any) => any, -): Uint8Array; -export function applyAwarenessUpdate( - awareness: Awareness, - update: Uint8Array, - origin: any, -): void; - -/** - * Core Yjs defines two message types: - * • YjsSyncStep1: Includes the State Set of the sending client. When received, the client should reply with YjsSyncStep2. - * • YjsSyncStep2: Includes all missing structs and the complete delete set. When received, the client is assured that it - * received all information from the remote client. - * - * In a peer-to-peer network, you may want to introduce a SyncDone message type. Both parties should initiate the connection - * with SyncStep1. When a client received SyncStep2, it should reply with SyncDone. When the local client received both - * SyncStep2 and SyncDone, it is assured that it is synced to the remote client. - * - * In a client-server model, you want to handle this differently: The client should initiate the connection with SyncStep1. - * When the server receives SyncStep1, it should reply with SyncStep2 immediately followed by SyncStep1. The client replies - * with SyncStep2 when it receives SyncStep1. Optionally the server may send a SyncDone after it received SyncStep2, so the - * client knows that the sync is finished. There are two reasons for this more elaborated sync model: 1. This protocol can - * easily be implemented on top of http and websockets. 2. The server should only reply to requests, and not initiate them. - * Therefore it is necessary that the client initiates the sync. - * - * Construction of a message: - * [messageType : varUint, message definition..] - * - * Note: A message does not include information about the room name. This must to be handled by the upper layer protocol! - * - * stringify[messageType] stringifies a message definition (messageType is already read from the bufffer) - */ -export type messageYjsSyncStep1 = 0; -export type messageYjsSyncStep2 = 1; -export type messageYjsUpdate = 2; -export function writeSyncStep1(encoder: encoding.Encoder, doc: Y.Doc): void; -export function writeSyncStep2( - encoder: encoding.Encoder, - doc: Y.Doc, - encodedStateVector?: Uint8Array | undefined, -): void; -export function readSyncStep1( - decoder: decoding.Decoder, - encoder: encoding.Encoder, - doc: Y.Doc, -): void; -export function readSyncStep2( - decoder: decoding.Decoder, - doc: Y.Doc, - transactionOrigin: any, -): void; -export function writeUpdate( - encoder: encoding.Encoder, - update: Uint8Array, -): void; -export function readUpdate( - decoder: decoding.Decoder, - doc: Y.Doc, - transactionOrigin: any, -): void; -export function readSyncMessage( - decoder: decoding.Decoder, - encoder: encoding.Encoder, - doc: Y.Doc, - transactionOrigin: any, -): messageYjsSyncStep1 | messageYjsSyncStep2 | messageYjsUpdate; diff --git a/y-octo-node/yocto.d.ts b/y-octo-node/yocto.d.ts index 16e9cb6..1eed236 100644 --- a/y-octo-node/yocto.d.ts +++ b/y-octo-node/yocto.d.ts @@ -85,8 +85,7 @@ export declare class YMapKeyIterator { export declare class YMapValuesIterator { [Symbol.iterator](): Iterator } -export type YProtocol = Protocol -export declare class Protocol { +export declare class YProtocol { constructor(doc: Doc) encodeSyncStep(step: number, buffer?: Buffer | undefined | null): Buffer applySyncStep(buffer: Buffer): Buffer | null diff --git a/y-octo-node/yocto.js b/y-octo-node/yocto.js index aa54186..2ee8d5e 100644 --- a/y-octo-node/yocto.js +++ b/y-octo-node/yocto.js @@ -310,7 +310,7 @@ if (!nativeBinding) { throw new Error(`Failed to load native binding`) } -const { YArray, YArrayIterator, Awareness, Doc, encodeStateAsUpdate, encodeStateVector, compareStructStores, compareIds, createDeleteSetFromStructStore, equalDeleteSets, snapshot, encodeSnapshot, applyUpdate, mergeUpdates, isAbstractType, YMap, YMapEntriesIterator, YMapKeyIterator, YMapValuesIterator, Protocol, YText, Id, Store, DeleteSet, YSnapshot } = nativeBinding +const { YArray, YArrayIterator, Awareness, Doc, encodeStateAsUpdate, encodeStateVector, compareStructStores, compareIds, createDeleteSetFromStructStore, equalDeleteSets, snapshot, encodeSnapshot, applyUpdate, mergeUpdates, isAbstractType, YMap, YMapEntriesIterator, YMapKeyIterator, YMapValuesIterator, YProtocol, YText, Id, Store, DeleteSet, YSnapshot } = nativeBinding module.exports.YArray = YArray module.exports.YArrayIterator = YArrayIterator @@ -331,7 +331,7 @@ module.exports.YMap = YMap module.exports.YMapEntriesIterator = YMapEntriesIterator module.exports.YMapKeyIterator = YMapKeyIterator module.exports.YMapValuesIterator = YMapValuesIterator -module.exports.Protocol = Protocol +module.exports.YProtocol = YProtocol module.exports.YText = YText module.exports.Id = Id module.exports.Store = Store From 06e97a4c345a40bac1f63b7c67031b5d09938db0 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Tue, 3 Sep 2024 14:34:51 +0800 Subject: [PATCH 39/75] feat: add protocol wrapper --- y-octo-node/index.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/y-octo-node/index.ts b/y-octo-node/index.ts index 10a42a1..ce5ce63 100644 --- a/y-octo-node/index.ts +++ b/y-octo-node/index.ts @@ -84,6 +84,20 @@ export class Doc extends Y.Doc { } } +export class Protocol extends Y.YProtocol { + constructor(private readonly doc: Doc) { + super(doc); + } + + override applySyncStep(buffer: Buffer): Buffer | null { + try { + return super.applySyncStep(buffer); + } finally { + this.doc.triggerDiff(this.doc); + } + } +} + export class Array { private ytype?: { doc: Doc; array: Y.YArray }; private preliminary: any[] = []; From a4c656130e095ff53845492c0c752bbad2cdad3b Mon Sep 17 00:00:00 2001 From: DarkSky Date: Tue, 3 Sep 2024 15:23:11 +0800 Subject: [PATCH 40/75] fix: trigger diff logic --- y-octo-node/index.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/y-octo-node/index.ts b/y-octo-node/index.ts index ce5ce63..09f9d70 100644 --- a/y-octo-node/index.ts +++ b/y-octo-node/index.ts @@ -47,20 +47,28 @@ export class Doc extends Y.Doc { } triggerDiff(origin?: unknown): void { + let diff: Buffer | null = null; if (this.lastState) { - const diff = this.diff(this.lastState); - if (diff && !this.lastState.equals(diff)) { - this.lastState = diff; + diff = this.diff(this.lastState); + const state = this.encodeStateAsUpdateV1(); + if (!this.lastState.equals(state)) { + this.lastState = state; } else { return; } } else { this.lastState = this.encodeStateAsUpdateV1(); + diff = this.diff(this.lastState); } - if (this.lastState?.length) { + // skip empty diffs + if (!diff || diff.equals(new Uint8Array([0, 0]))) { + return; + } + + if (this.lastState?.length && diff?.length) { this.subscribers.forEach((callback) => - callback(new Uint8Array(this.lastState!), origin || this), + callback(new Uint8Array(diff), origin || this), ); } } From 34e8cd246024032a6adb282fc033a1f0c1ad47d1 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Tue, 3 Sep 2024 17:16:43 +0800 Subject: [PATCH 41/75] feat: apply update --- y-octo-node/index.ts | 6 ++++++ y-octo-node/tests/yjs/testHelper.ts | 9 +++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/y-octo-node/index.ts b/y-octo-node/index.ts index 09f9d70..f88a1fa 100644 --- a/y-octo-node/index.ts +++ b/y-octo-node/index.ts @@ -81,6 +81,12 @@ export class Doc extends Y.Doc { } } + override applyUpdate(update: Buffer): void { + this.transact(() => { + super.applyUpdate(update); + }); + } + override onUpdate( callback: (result: Uint8Array, origin?: unknown) => void, ): void { diff --git a/y-octo-node/tests/yjs/testHelper.ts b/y-octo-node/tests/yjs/testHelper.ts index dd836a9..1da1503 100644 --- a/y-octo-node/tests/yjs/testHelper.ts +++ b/y-octo-node/tests/yjs/testHelper.ts @@ -258,12 +258,9 @@ export const compare = (users: TestYOctoInstance[]) => { // This ensures that mergeUpdates works correctly const mergedDocs = users.map((user: { updates: any }, i) => { const ydoc = new Y.Doc(); - console.error( - `Merging user ${i}'s updates: ${user.updates - .map((u: any) => u.length) - .join(", ")}`, - ); - Y.applyUpdate(ydoc, Y.mergeUpdates(user.updates)); + console.error(`Merging user ${i}'s updates: ${user.updates.join(", ")}`); + ydoc.applyUpdate(Y.mergeUpdates(user.updates)); + console.error("Merged user", ydoc.getArray("array").toJSON()); return ydoc; }); users.push(...mergedDocs); From 78f85490ab9b7085af5d62c0f2cb55c5bd565470 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Tue, 3 Sep 2024 17:58:22 +0800 Subject: [PATCH 42/75] fix: get ytype --- y-octo-node/index.ts | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/y-octo-node/index.ts b/y-octo-node/index.ts index f88a1fa..ba36485 100644 --- a/y-octo-node/index.ts +++ b/y-octo-node/index.ts @@ -120,7 +120,13 @@ export class Array { return new Array(items); } - constructor(items: ArrayType[], ydoc?: Doc, yarray?: Y.YArray) { + static from_ytype(ytype?: { doc: Doc; array: Y.YArray }) { + const array = new Array(); + array.ytype = ytype; + return array; + } + + constructor(items: ArrayType[] = [], ydoc?: Doc, yarray?: Y.YArray) { this.preliminary = items; if (ydoc) this.integrate(ydoc, yarray); } @@ -242,7 +248,13 @@ export class Map { private ytype?: { doc: Doc; map: Y.YMap }; private preliminary: Record = {}; - constructor(items: Record, ydoc?: Doc, ymap?: Y.YMap) { + static from_ytype(ytype?: { doc: Doc; map: Y.YMap }) { + const map = new Map(); + map.ytype = ytype; + return map; + } + + constructor(items: Record = {}, ydoc?: Doc, ymap?: Y.YMap) { this.preliminary = items; if (ydoc) this.integrate(ydoc, ymap); } @@ -282,12 +294,26 @@ export class Map { } get(key: string): T { - return this.ytype ? this.ytype.map.get(key) : this.preliminary[key]; + if (this.ytype) { + const ret = this.ytype.map.get(key); + if (ret) { + if (ret instanceof Y.YArray) { + return Array.from_ytype({ doc: this.ytype.doc, array: ret }) as T; + } else if (ret instanceof Y.YMap) { + return Map.from_ytype({ doc: this.ytype.doc, map: ret }) as T; + } + } + return ret as T; + } else { + return this.preliminary[key]; + } } set(key: string, value: T) { + console.trace(value, this.ytype); if (this.ytype) { if (value instanceof Array || value instanceof Map) { + console.log("integrate", value); this.ytype.map.set(key, value.integrate(this.ytype.doc)); } else { this.ytype.map.set(key, value); From 9a3ce67543bf235fab2a0f58d4395c7dc0f2f652 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Wed, 4 Sep 2024 14:58:47 +0800 Subject: [PATCH 43/75] feat: enable more test --- y-octo-node/tests/yjs/y-array.spec.ts | 3 +-- y-octo-node/tests/yjs/y-map.spec.ts | 11 +++++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/y-octo-node/tests/yjs/y-array.spec.ts b/y-octo-node/tests/yjs/y-array.spec.ts index 2b88b9c..a88d673 100644 --- a/y-octo-node/tests/yjs/y-array.spec.ts +++ b/y-octo-node/tests/yjs/y-array.spec.ts @@ -126,8 +126,7 @@ test.skip("testDeleteInsert", (t) => { compare(users); }); -// TODO: impl sync protocol encode in rust -test.skip("testInsertThreeElementsTryRegetProperty", (t) => { +test("testInsertThreeElementsTryRegetProperty", (t) => { const { testConnector, users, array0, array1 } = init(gen, { users: 2 }); array0.insert(0, [1, true, false]); diff --git a/y-octo-node/tests/yjs/y-map.spec.ts b/y-octo-node/tests/yjs/y-map.spec.ts index 4e74e9c..fb8ec78 100644 --- a/y-octo-node/tests/yjs/y-map.spec.ts +++ b/y-octo-node/tests/yjs/y-map.spec.ts @@ -259,7 +259,7 @@ test("testSizeAndDeleteOfMapProperty", (t) => { testConnector.disconnectAll(); }); -test.skip("testGetAndSetAndDeleteOfMapProperty", (t) => { +test("testGetAndSetAndDeleteOfMapProperty", (t) => { const { testConnector, users, map0, map1 } = init(gen, { users: 3 }); map0.set("stuff", "c0"); @@ -268,10 +268,9 @@ test.skip("testGetAndSetAndDeleteOfMapProperty", (t) => { testConnector.flushAllMessages(); for (const user of users) { const u = user.getOrCreateMap("map"); - t.assert(u.get("stuff") === undefined); + t.is(u.get("stuff"), null); } compare(users); - testConnector.disconnectAll() }); test.skip("testSetAndClearOfMapProperties", (t) => { @@ -283,9 +282,9 @@ test.skip("testSetAndClearOfMapProperties", (t) => { testConnector.flushAllMessages(); for (const user of users) { const u = user.getOrCreateMap("map"); - t.assert(u.get("stuff") === undefined); - t.assert(u.get("otherstuff") === undefined); - t.assert(u.size === 0, `map size after clear is ${u.size}, expected 0`); + t.is(u.get("stuff"), null); + t.is(u.get("otherstuff"), null); + t.is(u.size, 0, `map size after clear is ${u.size}, expected 0`); } compare(users); }); From c697b521cc6976afd775ff40f2047f378bffd270 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Wed, 4 Sep 2024 15:40:45 +0800 Subject: [PATCH 44/75] chore: impl ytype convert --- y-octo-node/src/map.rs | 13 ++++++------ y-octo-node/src/text.rs | 1 + y-octo-node/src/{utils.rs => utils/mod.rs} | 8 ++++---- y-octo-node/src/utils/ytype.rs | 24 ++++++++++++++++++++++ 4 files changed, 36 insertions(+), 10 deletions(-) rename y-octo-node/src/{utils.rs => utils/mod.rs} (96%) create mode 100644 y-octo-node/src/utils/ytype.rs diff --git a/y-octo-node/src/map.rs b/y-octo-node/src/map.rs index d7bdcad..4cd7939 100644 --- a/y-octo-node/src/map.rs +++ b/y-octo-node/src/map.rs @@ -4,6 +4,7 @@ use y_octo::{Any, Map, Value}; use super::*; #[napi] +#[derive(Clone)] pub struct YMap { pub(crate) map: Map, } @@ -39,9 +40,9 @@ impl YMap { if let Some(value) = self.map.get(&key) { match value { Value::Any(any) => get_js_unknown_from_any(env, any).map(MixedYType::D), - Value::Array(array) => Ok(MixedYType::A(YArray::inner_new(array))), - Value::Map(map) => Ok(MixedYType::B(YMap::inner_new(map))), - Value::Text(text) => Ok(MixedYType::C(YText::inner_new(text))), + Value::Array(array) => Ok(YArray::inner_new(array).into()), + Value::Map(map) => Ok(YMap::inner_new(map).into()), + Value::Text(text) => Ok(YText::inner_new(text).into()), _ => env.get_null().map(|v| v.into_unknown()).map(MixedYType::D), } .map_err(anyhow::Error::from) @@ -60,15 +61,15 @@ impl YMap { match value { MixedRefYType::A(array) => { self.map.insert(key, array.array.clone())?; - Ok(MixedYType::A(YArray::inner_new(array.array.clone()))) + Ok(array.into()) } MixedRefYType::B(map) => { self.map.insert(key, map.map.clone())?; - Ok(MixedYType::B(YMap::inner_new(map.map.clone()))) + Ok(map.into()) } MixedRefYType::C(text) => { self.map.insert(key, text.text.clone())?; - Ok(MixedYType::C(YText::inner_new(text.text.clone()))) + Ok(text.into()) } MixedRefYType::D(unknown) => match unknown.get_type() { Ok(value_type) => match value_type { diff --git a/y-octo-node/src/text.rs b/y-octo-node/src/text.rs index 26da57f..711928a 100644 --- a/y-octo-node/src/text.rs +++ b/y-octo-node/src/text.rs @@ -4,6 +4,7 @@ use y_octo::Text; use super::*; #[napi] +#[derive(Clone)] pub struct YText { pub(crate) text: Text, } diff --git a/y-octo-node/src/utils.rs b/y-octo-node/src/utils/mod.rs similarity index 96% rename from y-octo-node/src/utils.rs rename to y-octo-node/src/utils/mod.rs index ef1f4fc..2e86f00 100644 --- a/y-octo-node/src/utils.rs +++ b/y-octo-node/src/utils/mod.rs @@ -1,10 +1,10 @@ -use napi::{bindgen_prelude::Either4, Env, Error, JsObject, JsUnknown, Result, Status, ValueType}; -use y_octo::{AHashMap, Any, HashMapExt, Value}; +mod ytype; use super::*; +use napi::{bindgen_prelude::Either4, Env, Error, JsObject, JsUnknown, Result, Status, ValueType}; +use y_octo::{AHashMap, Any, HashMapExt, Value}; -pub type MixedYType = Either4; -pub type MixedRefYType<'a> = Either4<&'a YArray, &'a YMap, &'a YText, JsUnknown>; +pub use ytype::*; pub fn get_js_unknown_from_any(env: Env, any: Any) -> Result { match any { diff --git a/y-octo-node/src/utils/ytype.rs b/y-octo-node/src/utils/ytype.rs new file mode 100644 index 0000000..efbeb4e --- /dev/null +++ b/y-octo-node/src/utils/ytype.rs @@ -0,0 +1,24 @@ +use super::*; + +pub type MixedRefYType<'a> = Either4<&'a YArray, &'a YMap, &'a YText, JsUnknown>; + +pub type MixedYType = Either4; + +//make a macro to generate the impl Into for each type + +macro_rules! impl_into_mixed_y_type { + ($type:ty, $enum:ident) => { + impl Into for $type { + fn into(self) -> MixedYType { + MixedYType::$enum(self.clone()) + } + } + }; +} + +impl_into_mixed_y_type!(YArray, A); +impl_into_mixed_y_type!(&YArray, A); +impl_into_mixed_y_type!(YMap, B); +impl_into_mixed_y_type!(&YMap, B); +impl_into_mixed_y_type!(YText, C); +impl_into_mixed_y_type!(&YText, C); From 80364775d67bb617306b333333e0a94d132c2fac Mon Sep 17 00:00:00 2001 From: DarkSky Date: Wed, 4 Sep 2024 16:41:09 +0800 Subject: [PATCH 45/75] feat: integrate mixed ytype convert --- y-octo-node/package.json | 2 +- y-octo-node/src/array.rs | 18 ++++++------------ y-octo-node/src/doc.rs | 7 +++++++ y-octo-node/src/lib.rs | 6 +++--- y-octo-node/src/map.rs | 17 ++++++----------- y-octo-node/src/utils/mod.rs | 10 ++++++++++ y-octo-node/tests/yjs/y-array.spec.ts | 2 +- y-octo-node/yocto.d.ts | 1 + 8 files changed, 35 insertions(+), 28 deletions(-) diff --git a/y-octo-node/package.json b/y-octo-node/package.json index 3ba5b80..3dcd79a 100644 --- a/y-octo-node/package.json +++ b/y-octo-node/package.json @@ -70,7 +70,7 @@ "--es-module-specifier-resolution=node" ], "files": [ - "tests/**/y-array.spec.ts" + "tests/**/*.spec.ts" ], "environmentVariables": { "TS_NODE_PROJECT": "./tests/tsconfig.json", diff --git a/y-octo-node/src/array.rs b/y-octo-node/src/array.rs index 31bd2c7..fe8df59 100644 --- a/y-octo-node/src/array.rs +++ b/y-octo-node/src/array.rs @@ -3,7 +3,7 @@ use napi::{ iterator::Generator, Env, JsFunction, JsUnknown, ValueType, }; -use y_octo::{Any, Array, Value}; +use y_octo::{Any, Array}; use super::*; @@ -31,18 +31,12 @@ impl YArray { #[napi(ts_generic_types = "T = unknown", ts_return_type = "T")] pub fn get(&self, env: Env, index: i64) -> Result { - if let Some(value) = self.array.get(index as u64) { - match value { - Value::Any(any) => get_js_unknown_from_any(env, any).map(MixedYType::D), - Value::Array(array) => Ok(MixedYType::A(YArray::inner_new(array))), - Value::Map(map) => Ok(MixedYType::B(YMap::inner_new(map))), - Value::Text(text) => Ok(MixedYType::C(YText::inner_new(text))), - _ => env.get_null().map(|v| v.into_unknown()).map(MixedYType::D), - } - .map_err(anyhow::Error::from) + let value = if let Some(value) = self.array.get(index as u64) { + get_mixed_y_type_from_value(env, value)? } else { - Ok(MixedYType::D(env.get_null()?.into_unknown())) - } + MixedYType::D(env.get_null()?.into_unknown()) + }; + Ok(value) } #[napi(ts_generic_types = "T = unknown", ts_return_type = "Array")] diff --git a/y-octo-node/src/doc.rs b/y-octo-node/src/doc.rs index 16d2a79..ebb3e2a 100644 --- a/y-octo-node/src/doc.rs +++ b/y-octo-node/src/doc.rs @@ -163,6 +163,13 @@ impl YDoc { self.doc.unsubscribe_all(); Ok(()) } + + #[napi] + pub fn destroy(&mut self) { + if let Err(e) = self.off_update() { + eprintln!("Failed to unsubscribe at doc destroy: {:?}", e); + } + } } #[cfg(test)] diff --git a/y-octo-node/src/lib.rs b/y-octo-node/src/lib.rs index f0294c9..dec8242 100644 --- a/y-octo-node/src/lib.rs +++ b/y-octo-node/src/lib.rs @@ -16,10 +16,10 @@ pub use awareness::*; pub use doc::*; pub use function::*; pub use map::*; +pub use protocol::*; pub use text::*; pub use types::*; -pub use protocol::*; use utils::{ - get_any_from_js_object, get_any_from_js_unknown, get_js_unknown_from_any, get_js_unknown_from_value, MixedRefYType, - MixedYType, + get_any_from_js_object, get_any_from_js_unknown, get_js_unknown_from_any, get_js_unknown_from_value, + get_mixed_y_type_from_value, MixedRefYType, MixedYType, }; diff --git a/y-octo-node/src/map.rs b/y-octo-node/src/map.rs index 4cd7939..971f47c 100644 --- a/y-octo-node/src/map.rs +++ b/y-octo-node/src/map.rs @@ -37,18 +37,13 @@ impl YMap { #[napi(ts_generic_types = "T = unknown", ts_return_type = "T")] pub fn get(&self, env: Env, key: String) -> Result { - if let Some(value) = self.map.get(&key) { - match value { - Value::Any(any) => get_js_unknown_from_any(env, any).map(MixedYType::D), - Value::Array(array) => Ok(YArray::inner_new(array).into()), - Value::Map(map) => Ok(YMap::inner_new(map).into()), - Value::Text(text) => Ok(YText::inner_new(text).into()), - _ => env.get_null().map(|v| v.into_unknown()).map(MixedYType::D), - } - .map_err(anyhow::Error::from) + let value = if let Some(value) = self.map.get(&key) { + get_mixed_y_type_from_value(env, value)? } else { - Ok(MixedYType::D(env.get_null()?.into_unknown())) - } + MixedYType::D(env.get_null()?.into_unknown()) + }; + + Ok(value) } #[napi( diff --git a/y-octo-node/src/utils/mod.rs b/y-octo-node/src/utils/mod.rs index 2e86f00..7a53761 100644 --- a/y-octo-node/src/utils/mod.rs +++ b/y-octo-node/src/utils/mod.rs @@ -50,6 +50,16 @@ pub fn get_js_unknown_from_value(env: Env, value: Value) -> Result { } } +pub fn get_mixed_y_type_from_value(env: Env, value: Value) -> Result { + match value { + Value::Any(any) => get_js_unknown_from_any(env, any).map(MixedYType::D), + Value::Array(array) => Ok(YArray::inner_new(array).into()), + Value::Map(map) => Ok(YMap::inner_new(map).into()), + Value::Text(text) => Ok(YText::inner_new(text).into()), + _ => env.get_null().map(|v| v.into_unknown()).map(MixedYType::D), + } +} + pub fn get_any_from_js_object(object: JsObject) -> Result { if let Ok(length) = object.get_array_length() { let mut array = Vec::with_capacity(length as usize); diff --git a/y-octo-node/tests/yjs/y-array.spec.ts b/y-octo-node/tests/yjs/y-array.spec.ts index a88d673..de0d027 100644 --- a/y-octo-node/tests/yjs/y-array.spec.ts +++ b/y-octo-node/tests/yjs/y-array.spec.ts @@ -434,7 +434,7 @@ test("testIteratingArrayContainingTypes", (t) => { for (const item of arr.iter()) { t.is(item.get("value"), cnt++, "value is correct"); } - // y.destroy(); + y.destroy(); }); let _uniqueNumber = 0; diff --git a/y-octo-node/yocto.d.ts b/y-octo-node/yocto.d.ts index 1eed236..b87817e 100644 --- a/y-octo-node/yocto.d.ts +++ b/y-octo-node/yocto.d.ts @@ -59,6 +59,7 @@ export declare class Doc { gc(): void onUpdate(callback: (result: Uint8Array) => void): void offUpdate(): void + destroy(): void } export declare class YMap { get length(): number From f8ad20e701a16203eef6260ef0a3ca4452dfcece Mon Sep 17 00:00:00 2001 From: DarkSky Date: Wed, 4 Sep 2024 17:23:36 +0800 Subject: [PATCH 46/75] fix: should skip deleted items at find index --- y-octo/src/doc/types/array.rs | 17 +++++++++++++++++ y-octo/src/doc/types/list/mod.rs | 11 +++++++++++ 2 files changed, 28 insertions(+) diff --git a/y-octo/src/doc/types/array.rs b/y-octo/src/doc/types/array.rs index 04b8390..9d160a7 100644 --- a/y-octo/src/doc/types/array.rs +++ b/y-octo/src/doc/types/array.rs @@ -102,6 +102,23 @@ mod tests { }); } + #[test] + fn test_yarray_delete() { + let options = DocOptions::default(); + + loom_model!({ + let doc = Doc::with_options(options.clone()); + let mut array = doc.get_or_create_array("abc").unwrap(); + + array.insert(0, " ").unwrap(); + array.insert(0, "Hello").unwrap(); + array.insert(2, "World").unwrap(); + array.remove(0, 2).unwrap(); + + assert_eq!(array.get(0).unwrap(), Value::Any(Any::String("World".into()))); + }); + } + #[test] #[cfg_attr(miri, ignore)] fn test_ytext_equal() { diff --git a/y-octo/src/doc/types/list/mod.rs b/y-octo/src/doc/types/list/mod.rs index fcfe7ad..b885fd5 100644 --- a/y-octo/src/doc/types/list/mod.rs +++ b/y-octo/src/doc/types/list/mod.rs @@ -6,6 +6,7 @@ pub(crate) use search_marker::MarkerList; use super::*; +#[derive(Debug)] pub(crate) struct ItemPosition { pub parent: YTypeRef, pub left: ItemRef, @@ -97,6 +98,16 @@ pub(crate) trait ListType: AsInner { } }; + // avoid the first item of the list being deleted + while let Some(item) = pos.right.get() { + if item.deleted() { + pos.right = item.right.clone(); + continue; + } else { + break; + } + } + while remaining > 0 { if let Some(item) = pos.right.get() { if !item.deleted() { From 2f408e86dc7b499f33c470787705a4e04e5a6381 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Thu, 5 Sep 2024 11:36:56 +0800 Subject: [PATCH 47/75] feat: distinguish between null and undefined --- y-octo-node/src/array.rs | 8 +++++--- y-octo-node/src/map.rs | 6 +++++- y-octo-node/src/utils/mod.rs | 3 ++- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/y-octo-node/src/array.rs b/y-octo-node/src/array.rs index fe8df59..9e8e09d 100644 --- a/y-octo-node/src/array.rs +++ b/y-octo-node/src/array.rs @@ -93,9 +93,11 @@ impl YArray { .map_err(anyhow::Error::from), MixedRefYType::D(unknown) => match unknown.get_type() { Ok(value_type) => match value_type { - ValueType::Undefined | ValueType::Null => { - self.array.insert(index as u64, Any::Null).map_err(anyhow::Error::from) - } + ValueType::Undefined => self + .array + .insert(index as u64, Any::Undefined) + .map_err(anyhow::Error::from), + ValueType::Null => self.array.insert(index as u64, Any::Null).map_err(anyhow::Error::from), ValueType::Boolean => match unknown.coerce_to_bool().and_then(|v| v.get_value()) { Ok(boolean) => self.array.insert(index as u64, boolean).map_err(anyhow::Error::from), Err(e) => Err(anyhow::Error::new(e).context("Failed to coerce value to boolean")), diff --git a/y-octo-node/src/map.rs b/y-octo-node/src/map.rs index 971f47c..97fc69b 100644 --- a/y-octo-node/src/map.rs +++ b/y-octo-node/src/map.rs @@ -68,7 +68,11 @@ impl YMap { } MixedRefYType::D(unknown) => match unknown.get_type() { Ok(value_type) => match value_type { - ValueType::Undefined | ValueType::Null => { + ValueType::Undefined => { + self.map.insert(key, Any::Undefined)?; + Ok(MixedYType::D(env.get_undefined().map(|v| v.into_unknown())?)) + } + ValueType::Null => { self.map.insert(key, Any::Null)?; Ok(MixedYType::D(env.get_null().map(|v| v.into_unknown())?)) } diff --git a/y-octo-node/src/utils/mod.rs b/y-octo-node/src/utils/mod.rs index 7a53761..f61c6c3 100644 --- a/y-octo-node/src/utils/mod.rs +++ b/y-octo-node/src/utils/mod.rs @@ -90,7 +90,8 @@ pub fn get_any_from_js_object(object: JsObject) -> Result { pub fn get_any_from_js_unknown(js_unknown: JsUnknown) -> Result { match js_unknown.get_type()? { - ValueType::Undefined | ValueType::Null => Ok(Any::Null), + ValueType::Undefined => Ok(Any::Undefined), + ValueType::Null => Ok(Any::Null), ValueType::Boolean => Ok(js_unknown.coerce_to_bool().and_then(|v| v.get_value())?.into()), ValueType::Number => Ok(js_unknown .coerce_to_number() From 3f268520a8b27af2ac7f963447f7d3b4b0aaa56b Mon Sep 17 00:00:00 2001 From: DarkSky Date: Thu, 5 Sep 2024 14:27:12 +0800 Subject: [PATCH 48/75] feat: improve test helper's assert & typing --- y-octo-node/tests/yjs/testHelper.ts | 82 ++++++++++++++++------------- 1 file changed, 46 insertions(+), 36 deletions(-) diff --git a/y-octo-node/tests/yjs/testHelper.ts b/y-octo-node/tests/yjs/testHelper.ts index 1da1503..5775f4c 100644 --- a/y-octo-node/tests/yjs/testHelper.ts +++ b/y-octo-node/tests/yjs/testHelper.ts @@ -1,8 +1,8 @@ -import assert, { deepEqual } from "node:assert"; import * as prng from "lib0/prng"; import * as object from "lib0/object"; import * as map from "lib0/map"; import * as Y from "../../index"; +import { ExecutionContext } from "ava"; if (typeof window !== "undefined") { // @ts-ignore @@ -15,13 +15,11 @@ if (typeof window !== "undefined") { */ const broadcastMessage = (y: TestYOctoInstance, m: Uint8Array) => { if (y.tc.onlineConns.has(y)) { - y.tc.onlineConns.forEach( - (remoteYInstance: { _receive: (arg0: any, arg1: any) => void }) => { - if (remoteYInstance !== y) { - remoteYInstance._receive(m, y); - } - }, - ); + y.tc.onlineConns.forEach((remoteYInstance: TestYOctoInstance) => { + if (remoteYInstance !== y) { + remoteYInstance._receive(m, y); + } + }); } }; @@ -41,7 +39,6 @@ export class TestYOctoInstance extends Y.Doc { // set up observe on local model this.onUpdate((update, origin) => { if (origin !== testConnector) { - console.error("broadcasting update"); broadcastMessage( this, this.protocol.encodeSyncStep(3, Buffer.from(update)), @@ -100,14 +97,10 @@ export class TestYOctoInstance extends Y.Doc { * I think it makes sense. Deal with it. */ export class TestConnector { - allConns: Set; - onlineConns: Set; - prng: prng.PRNG; - constructor(gen: prng.PRNG) { - this.allConns = new Set(); - this.onlineConns = new Set(); - this.prng = gen; - } + readonly allConns: Set = new Set(); + readonly onlineConns: Set = new Set(); + + constructor(readonly prng: prng.PRNG) {} createY(clientID: number) { return new TestYOctoInstance(this, clientID); @@ -251,19 +244,21 @@ export const init = ( * 4. disconnect & reconnect all (so gc is propagated) * 5. compare os, ds, ss */ -export const compare = (users: TestYOctoInstance[]) => { +export const compare = (t: ExecutionContext, users: TestYOctoInstance[]) => { users.forEach((u) => u.connect()); while (users[0].tc.flushAllMessages()) {} // eslint-disable-line // For each document, merge all received document updates with Y.mergeUpdates and create a new document which will be added to the list of "users" // This ensures that mergeUpdates works correctly - const mergedDocs = users.map((user: { updates: any }, i) => { - const ydoc = new Y.Doc(); - console.error(`Merging user ${i}'s updates: ${user.updates.join(", ")}`); - ydoc.applyUpdate(Y.mergeUpdates(user.updates)); - console.error("Merged user", ydoc.getArray("array").toJSON()); - return ydoc; - }); - users.push(...mergedDocs); + + // TODO(@darkskygit): enable this for test + // const mergedDocs = users.map((user: { updates: any }, i) => { + // const ydoc = new Y.Doc(); + // console.error(`Merging user ${i}'s updates: ${user.updates.join(", ")}`); + // ydoc.applyUpdate(Y.mergeUpdates(user.updates)); + // console.error("Merged user", ydoc.getArray("array").toJSON()); + // return ydoc; + // }); + // users.push(...mergedDocs); const userArrayValues = users.map((u) => u.getArray("array").toJSON()); const userMapValues = users.map((u) => u.getMap("map").toJson()); // const userXmlValues = users.map( @@ -280,29 +275,40 @@ export const compare = (users: TestYOctoInstance[]) => { // t.assert(u.store.pendingStructs === null); // } // Test Array iterator - deepEqual( + t.deepEqual( users[0].getArray("array").toArray(), Array.from(users[0].getArray("array").iter()), + "Array iterator does not match", ); // Test Map iterator const ymapkeys: any[] = Array.from(users[0].getMap("map").keys()); - assert(ymapkeys.length === Object.keys(userMapValues[0]).length); - ymapkeys.forEach((key) => assert(object.hasProperty(userMapValues[0], key))); + t.is( + ymapkeys.length, + Object.keys(userMapValues[0]).length, + "Map keys do not match", + ); + ymapkeys.forEach((key) => + t.assert(object.hasProperty(userMapValues[0], key)), + ); const mapRes: Record = {}; for (const [k, v] of users[0].getMap("map").entries()) { mapRes[k] = Y.isAbstractType(v) ? v.toJSON() : v; } - deepEqual(userMapValues[0], mapRes); + t.deepEqual(userMapValues[0], mapRes, "Map values do not match"); // Compare all users for (let i = 0; i < users.length - 1; i++) { - deepEqual(userArrayValues[i].length, users[i].getArray("array").length); - deepEqual( + t.deepEqual( + userArrayValues[i].length, + users[i].getArray("array").length, + `array${i}.length !== array${i + 1}.length`, + ); + t.deepEqual( userArrayValues[i], userArrayValues[i + 1], `array${i} !== array${i + 1}`, ); - deepEqual( + t.deepEqual( userMapValues[i], userMapValues[i + 1], `map${i} !== map${i + 1}`, @@ -330,13 +336,16 @@ export const compare = (users: TestYOctoInstance[]) => { // return true; // }, // ); - deepEqual(Y.encodeStateVector(users[i]), Y.encodeStateVector(users[i + 1])); + t.deepEqual( + Y.encodeStateVector(users[i]), + Y.encodeStateVector(users[i + 1]), + ); Y.equalDeleteSets( Y.createDeleteSetFromStructStore(users[i].store), Y.createDeleteSetFromStructStore(users[i + 1].store), ); Y.compareStructStores(users[i].store, users[i + 1].store); - deepEqual( + t.deepEqual( Y.encodeSnapshot(Y.snapshot(users[i])), Y.encodeSnapshot(Y.snapshot(users[i + 1])), ); @@ -345,6 +354,7 @@ export const compare = (users: TestYOctoInstance[]) => { }; export const applyRandomTests = ( + t: ExecutionContext, gen: prng.PRNG, mods: unknown[], iterations: number, @@ -371,6 +381,6 @@ export const applyRandomTests = ( const test: any = prng.oneOf(gen, mods); test(users[user], gen, result.testObjects?.[user]); } - compare(users); + compare(t, users); return result; }; From 3987535873607dfa4902514c40913f1c02204772 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Thu, 5 Sep 2024 15:16:30 +0800 Subject: [PATCH 49/75] feat: enable more array test --- y-octo-node/tests/yjs/y-array.spec.ts | 76 +++++++++++++-------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/y-octo-node/tests/yjs/y-array.spec.ts b/y-octo-node/tests/yjs/y-array.spec.ts index de0d027..eaf5b2a 100644 --- a/y-octo-node/tests/yjs/y-array.spec.ts +++ b/y-octo-node/tests/yjs/y-array.spec.ts @@ -112,7 +112,7 @@ test("testLengthIssue2", (t) => { console.log(next.toArray()); }); -test.skip("testDeleteInsert", (t) => { +test("testDeleteInsert", (t) => { const { users, array0 } = init(gen, { users: 2 }); array0.delete(0, 0); @@ -123,7 +123,7 @@ test.skip("testDeleteInsert", (t) => { t.notThrows(() => { array0.delete(1, 0); }, "Does not throw when deleting zero elements with valid position 1"); - compare(users); + compare(t, users); }); test("testInsertThreeElementsTryRegetProperty", (t) => { @@ -133,19 +133,19 @@ test("testInsertThreeElementsTryRegetProperty", (t) => { t.deepEqual(array0.toJSON(), [1, true, false], ".toJSON() works"); testConnector.flushAllMessages(); t.deepEqual(array1.toJSON(), [1, true, false], ".toJSON() works after sync"); - compare(users); + compare(t, users); }); -test.skip("testConcurrentInsertWithThreeConflicts", (t) => { +test("testConcurrentInsertWithThreeConflicts", (t) => { const { users, array0, array1, array2 } = init(gen, { users: 3 }); array0.insert(0, [0]); array1.insert(0, [1]); array2.insert(0, [2]); - compare(users); + compare(t, users); }); -test.skip("testConcurrentInsertDeleteWithThreeConflicts", (t) => { +test("testConcurrentInsertDeleteWithThreeConflicts", (t) => { const { testConnector, users, array0, array1, array2 } = init(gen, { users: 3, }); @@ -156,10 +156,10 @@ test.skip("testConcurrentInsertDeleteWithThreeConflicts", (t) => { array1.delete(0); array1.delete(1, 1); array2.insert(1, [2]); - compare(users); + compare(t, users); }); -test.skip("testInsertionsInLateSync", (t) => { +test("testInsertionsInLateSync", (t) => { const { testConnector, users, array0, array1, array2 } = init(gen, { users: 3, }); @@ -174,10 +174,10 @@ test.skip("testInsertionsInLateSync", (t) => { users[1].connect(); users[2].connect(); testConnector.flushAllMessages(); - compare(users); + compare(t, users); }); -test.skip("testDisconnectReallyPreventsSendingMessages", (t) => { +test("testDisconnectReallyPreventsSendingMessages", (t) => { const { testConnector, users, array0, array1 } = init(gen, { users: 3 }); array0.insert(0, ["x", "y"]); @@ -190,10 +190,10 @@ test.skip("testDisconnectReallyPreventsSendingMessages", (t) => { t.deepEqual(array1.toJSON(), ["x", "user1", "y"]); users[1].connect(); users[2].connect(); - compare(users); + compare(t, users); }); -test.skip("testDeletionsInLateSync", (t) => { +test("testDeletionsInLateSync", (t) => { const { testConnector, users, array0, array1 } = init(gen, { users: 2 }); array0.insert(0, ["x", "y"]); @@ -202,7 +202,7 @@ test.skip("testDeletionsInLateSync", (t) => { array1.delete(1, 1); array0.delete(0, 2); users[1].connect(); - compare(users); + compare(t, users); }); test.skip("testInsertThenMergeDeleteOnSync", (t) => { @@ -213,7 +213,7 @@ test.skip("testInsertThenMergeDeleteOnSync", (t) => { users[0].disconnect(); array1.delete(0, 3); users[0].connect(); - compare(users); + compare(t, users); }); test.skip("testInsertAndDeleteEvents", (t) => { @@ -232,7 +232,7 @@ test.skip("testInsertAndDeleteEvents", (t) => { array0.delete(0, 2); assert(event !== null); event = null; - compare(users); + compare(t, users); }); test.skip("testNestedObserverEvents", (t) => { @@ -253,7 +253,7 @@ test.skip("testNestedObserverEvents", (t) => { array0.insert(0, [0]); t.deepEqual(vals, [0, 1]); t.deepEqual(array0.toArray(), [0, 1]); - compare(users); + compare(t, users); }); test.skip("testInsertAndDeleteEventsForTypes", (t) => { @@ -269,7 +269,7 @@ test.skip("testInsertAndDeleteEventsForTypes", (t) => { array0.delete(0); assert(event !== null); event = null; - compare(users); + compare(t, users); }); /** @@ -344,7 +344,7 @@ test.skip("testChangeEvent", (t) => { changes !== null && changes.added.size === 1 && changes.deleted.size === 0, ); t.deepEqual(changes.delta, [{ retain: 1 }, { insert: [0.1] }]); - compare(users); + compare(t, users); }); test.skip("testInsertAndDeleteEventsForTypes2", (t) => { @@ -362,7 +362,7 @@ test.skip("testInsertAndDeleteEventsForTypes2", (t) => { ); array0.delete(1); t.is(events.length, 2, "Event is triggered exactly once for deletion"); - compare(users); + compare(t, users); }); /** @@ -392,7 +392,7 @@ test.skip("testGarbageCollector", (t) => { array0.delete(0, 3); users[0].connect(); testConnector.flushAllMessages(); - compare(users); + compare(t, users); }); test.skip("testEventTargetIsSetCorrectlyOnLocal", (t) => { @@ -404,7 +404,7 @@ test.skip("testEventTargetIsSetCorrectlyOnLocal", (t) => { }); array0.insert(0, ["stuff"]); assert(event.target === array0, '"target" property is set correctly'); - compare(users); + compare(t, users); }); test.skip("testEventTargetIsSetCorrectlyOnRemote", (t) => { @@ -417,7 +417,7 @@ test.skip("testEventTargetIsSetCorrectlyOnRemote", (t) => { array1.insert(0, ["stuff"]); testConnector.flushAllMessages(); assert(event.target === array0, '"target" property is set correctly'); - compare(users); + compare(t, users); }); test("testIteratingArrayContainingTypes", (t) => { @@ -502,68 +502,68 @@ const arrayTransactions: Array< ]; test.skip("testRepeatGeneratingYarrayTests6", (t) => { - applyRandomTests(gen, arrayTransactions, 6); + applyRandomTests(t, gen, arrayTransactions, 6); }); test.skip("testRepeatGeneratingYarrayTests40", (t) => { - applyRandomTests(gen, arrayTransactions, 40); + applyRandomTests(t, gen, arrayTransactions, 40); }); test.skip("testRepeatGeneratingYarrayTests42", (t) => { - applyRandomTests(gen, arrayTransactions, 42); + applyRandomTests(t, gen, arrayTransactions, 42); }); test.skip("testRepeatGeneratingYarrayTests43", (t) => { - applyRandomTests(gen, arrayTransactions, 43); + applyRandomTests(t, gen, arrayTransactions, 43); }); test.skip("testRepeatGeneratingYarrayTests44", (t) => { - applyRandomTests(gen, arrayTransactions, 44); + applyRandomTests(t, gen, arrayTransactions, 44); }); test.skip("testRepeatGeneratingYarrayTests45", (t) => { - applyRandomTests(gen, arrayTransactions, 45); + applyRandomTests(t, gen, arrayTransactions, 45); }); test.skip("testRepeatGeneratingYarrayTests46", (t) => { - applyRandomTests(gen, arrayTransactions, 46); + applyRandomTests(t, gen, arrayTransactions, 46); }); test.skip("testRepeatGeneratingYarrayTests300", (t) => { - applyRandomTests(gen, arrayTransactions, 300); + applyRandomTests(t, gen, arrayTransactions, 300); }); test.skip("testRepeatGeneratingYarrayTests400", (t) => { - applyRandomTests(gen, arrayTransactions, 400); + applyRandomTests(t, gen, arrayTransactions, 400); }); test.skip("testRepeatGeneratingYarrayTests500", (t) => { - applyRandomTests(gen, arrayTransactions, 500); + applyRandomTests(t, gen, arrayTransactions, 500); }); test.skip("testRepeatGeneratingYarrayTests600", (t) => { - applyRandomTests(gen, arrayTransactions, 600); + applyRandomTests(t, gen, arrayTransactions, 600); }); test.skip("testRepeatGeneratingYarrayTests1000", (t) => { - applyRandomTests(gen, arrayTransactions, 1000); + applyRandomTests(t, gen, arrayTransactions, 1000); }); test.skip("testRepeatGeneratingYarrayTests1800", (t) => { - applyRandomTests(gen, arrayTransactions, 1800); + applyRandomTests(t, gen, arrayTransactions, 1800); }); test.skip("testRepeatGeneratingYarrayTests3000", (t) => { if (!production) return; - applyRandomTests(gen, arrayTransactions, 3000); + applyRandomTests(t, gen, arrayTransactions, 3000); }); test.skip("testRepeatGeneratingYarrayTests5000", (t) => { if (!production) return; - applyRandomTests(gen, arrayTransactions, 5000); + applyRandomTests(t, gen, arrayTransactions, 5000); }); test.skip("testRepeatGeneratingYarrayTests30000", (t) => { if (!production) return; - applyRandomTests(gen, arrayTransactions, 30000); + applyRandomTests(t, gen, arrayTransactions, 30000); }); From a96bed7b10d8ff73285d5c7831aec81945216288 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Thu, 5 Sep 2024 16:43:15 +0800 Subject: [PATCH 50/75] feat: remove node test --- y-octo-node/index.ts | 1 - y-octo-node/package.json | 2 +- y-octo-node/tests/yjs/y-map.spec.ts | 88 ++++++++++++++-------------- y-octo-node/tests/yjs/y-text.spec.ts | 71 +++++++++++----------- 4 files changed, 79 insertions(+), 83 deletions(-) diff --git a/y-octo-node/index.ts b/y-octo-node/index.ts index ba36485..bc2c3eb 100644 --- a/y-octo-node/index.ts +++ b/y-octo-node/index.ts @@ -310,7 +310,6 @@ export class Map { } set(key: string, value: T) { - console.trace(value, this.ytype); if (this.ytype) { if (value instanceof Array || value instanceof Map) { console.log("integrate", value); diff --git a/y-octo-node/package.json b/y-octo-node/package.json index 3dcd79a..6e2951f 100644 --- a/y-octo-node/package.json +++ b/y-octo-node/package.json @@ -38,7 +38,7 @@ "build": "napi build --platform --release --no-const-enum --dts yocto.d.ts --js yocto.js", "build:debug": "napi build --platform --no-const-enum --dts yocto.d.ts --js yocto.js", "universal": "napi universal", - "test": "ava --concurrency 1 --serial --timeout 1s", + "test": "ava --concurrency 1 --serial --timeout 5s", "test:watch": "yarn exec tsx ./scripts/run-test.mts all --watch", "test:coverage": "NODE_OPTIONS=\"--import tsx\" c8 node ./scripts/run-test.mts all", "version": "napi version" diff --git a/y-octo-node/tests/yjs/y-map.spec.ts b/y-octo-node/tests/yjs/y-map.spec.ts index fb8ec78..48a546b 100644 --- a/y-octo-node/tests/yjs/y-map.spec.ts +++ b/y-octo-node/tests/yjs/y-map.spec.ts @@ -1,6 +1,5 @@ -import assert, { deepEqual } from "node:assert"; import { randomInt } from "node:crypto"; -import test from "ava"; +import test, { ExecutionContext } from "ava"; import { init, compare, applyRandomTests } from "./testHelper.js"; @@ -70,7 +69,7 @@ test("testMapHavingIterableAsConstructorParamTests", (t) => { testConnector.disconnectAll(); }); -test.skip("testBasicMapTests", (t) => { +test("testBasicMapTests", (t) => { const { testConnector, users, map0, map1, map2 } = init(gen, { users: 3 }); users[2].disconnect(); @@ -171,10 +170,10 @@ test.skip("testBasicMapTests", (t) => { map2.get("y-map").get("y-array").get(0) === -1, "client 2 received the update (type) - was disconnected", ); - compare(users); + compare(t, users); }); -test.skip("testGetAndSetOfMapProperty", (t) => { +test("testGetAndSetOfMapProperty", (t) => { const { testConnector, users, map0 } = init(gen, { users: 2 }); map0.set("stuff", "stuffy"); @@ -190,7 +189,7 @@ test.skip("testGetAndSetOfMapProperty", (t) => { t.assert(u.get("undefined") === undefined, "undefined"); t.deepEqual(u.get("null"), null, "null"); } - compare(users); + compare(t, users); }); test.skip("testYmapSetsYmap", (t) => { @@ -200,7 +199,7 @@ test.skip("testYmapSetsYmap", (t) => { t.assert(Y.compareIds(map0.get("Map").itemId, map.itemId)); map.set("one", 1); t.deepEqual(map.get("one"), 1); - compare(users); + compare(t, users); }); test.skip("testYmapSetsYarray", (t) => { @@ -211,7 +210,7 @@ test.skip("testYmapSetsYarray", (t) => { array.insert(0, [1, 2, 3]); // @ts-ignore t.deepEqual(map0.toJSON(), { Array: [1, 2, 3] }); - compare(users); + compare(t, users); }); test.skip("testGetAndSetOfMapPropertySyncs", (t) => { @@ -224,7 +223,7 @@ test.skip("testGetAndSetOfMapPropertySyncs", (t) => { const u = user.getOrCreateMap("map"); t.deepEqual(u.get("stuff"), "stuffy"); } - compare(users); + compare(t, users); }); test.skip("testGetAndSetOfMapPropertyWithConflict", (t) => { @@ -237,7 +236,7 @@ test.skip("testGetAndSetOfMapPropertyWithConflict", (t) => { const u = user.getOrCreateMap("map"); t.deepEqual(u.get("stuff"), "c1"); } - compare(users); + compare(t, users); }); test("testSizeAndDeleteOfMapProperty", (t) => { @@ -270,7 +269,7 @@ test("testGetAndSetAndDeleteOfMapProperty", (t) => { const u = user.getOrCreateMap("map"); t.is(u.get("stuff"), null); } - compare(users); + compare(t, users); }); test.skip("testSetAndClearOfMapProperties", (t) => { @@ -286,7 +285,7 @@ test.skip("testSetAndClearOfMapProperties", (t) => { t.is(u.get("otherstuff"), null); t.is(u.size, 0, `map size after clear is ${u.size}, expected 0`); } - compare(users); + compare(t, users); }); test.skip("testSetAndClearOfMapPropertiesWithConflicts", (t) => { @@ -311,7 +310,7 @@ test.skip("testSetAndClearOfMapPropertiesWithConflicts", (t) => { t.assert(u.get("otherstuff") === undefined); t.assert(u.size === 0, `map size after clear is ${u.size}, expected 0`); } - compare(users); + compare(t, users); }); test.skip("testGetAndSetOfMapPropertyWithThreeConflicts", (t) => { @@ -326,7 +325,7 @@ test.skip("testGetAndSetOfMapPropertyWithThreeConflicts", (t) => { const u = user.getOrCreateMap("map"); t.deepEqual(u.get("stuff"), "c3"); } - compare(users); + compare(t, users); }); test.skip("testGetAndSetAndDeleteOfMapPropertyWithThreeConflicts", (t) => { @@ -349,7 +348,7 @@ test.skip("testGetAndSetAndDeleteOfMapPropertyWithThreeConflicts", (t) => { const u = user.getOrCreateMap("map"); t.assert(u.get("stuff") === undefined); } - compare(users); + compare(t, users); }); test.skip("testObserveDeepProperties", (t) => { @@ -384,7 +383,7 @@ test.skip("testObserveDeepProperties", (t) => { t.assert(Y.compareIds(dmap1.itemId, dmap3.itemId)); // @ts-ignore we want the possibility of dmapid being undefined t.assert(compareIDs(dmap1._item.id, dmapid)); - compare(users); + compare(t, users); }); test.skip("testObserversUsingObservedeep", (t) => { @@ -403,7 +402,7 @@ test.skip("testObserversUsingObservedeep", (t) => { map0.get("map").get("array").insert(0, ["content"]); t.assert(calls === 3); t.deepEqual(pathes, [[], ["map"], ["map", "array"]]); - compare(users); + compare(t, users); }); test.skip("testPathsOfSiblingEvents", (t) => { @@ -426,7 +425,7 @@ test.skip("testPathsOfSiblingEvents", (t) => { }); t.assert(calls === 1); t.deepEqual(pathes, [["map"], ["map", "text1"]]); - compare(users); + compare(t, users); }); // TODO: Test events in Y.Map @@ -435,11 +434,12 @@ test.skip("testPathsOfSiblingEvents", (t) => { * @param {Object} should */ const compareEvent = ( + t: ExecutionContext, is: { [s: string]: any }, should: { [s: string]: any }, ) => { for (const key in should) { - deepEqual(should[key], is[key]); + t.deepEqual(should[key], is[key]); } }; @@ -453,13 +453,13 @@ test.skip("testThrowsAddAndUpdateAndDeleteEvents", (t) => { event = e; // just put it on event, should be thrown synchronously anyway }); map0.set("stuff", 4); - compareEvent(event, { + compareEvent(t, event, { target: map0, keysChanged: new Set(["stuff"]), }); // update, oldValue is in contents map0.set("stuff", new Y.Array()); - compareEvent(event, { + compareEvent(t, event, { target: map0, keysChanged: new Set(["stuff"]), }); @@ -467,11 +467,11 @@ test.skip("testThrowsAddAndUpdateAndDeleteEvents", (t) => { map0.set("stuff", 5); // delete map0.delete("stuff"); - compareEvent(event, { + compareEvent(t, event, { keysChanged: new Set(["stuff"]), target: map0, }); - compare(users); + compare(t, users); }); test.skip("testThrowsDeleteEventsOnClear", (t) => { @@ -488,11 +488,11 @@ test.skip("testThrowsDeleteEventsOnClear", (t) => { map0.set("otherstuff", new Y.Array()); // clear map0.clear(); - compareEvent(event, { + compareEvent(t, event, { keysChanged: new Set(["stuff", "otherstuff"]), target: map0, }); - compare(users); + compare(t, users); }); test.skip("testChangeEvent", (t) => { @@ -557,7 +557,7 @@ test.skip("testChangeEvent", (t) => { keyChange.action === "add" && keyChange.oldValue === undefined, ); - compare(users); + compare(t, users); }); test.skip("testYmapEventExceptionsShouldCompleteTransaction", (t) => { @@ -619,7 +619,7 @@ test.skip("testYmapEventHasCorrectValueWhenSettingAPrimitive", (t) => { }); map0.set("stuff", 2); t.deepEqual(event.value, event.target.get(event.name)); - compare(users); + compare(t, users); }); test.skip("testYmapEventHasCorrectValueWhenSettingAPrimitiveFromOtherUser", (t) => { @@ -632,7 +632,7 @@ test.skip("testYmapEventHasCorrectValueWhenSettingAPrimitiveFromOtherUser", (t) map1.set("stuff", 2); testConnector.flushAllMessages(); t.deepEqual(event.value, event.target.get(event.name)); - compare(users); + compare(t, users); }); const mapTransactions: Array<(arg0: Y.Doc, arg1: prng.PRNG) => void> = [ @@ -658,68 +658,68 @@ const mapTransactions: Array<(arg0: Y.Doc, arg1: prng.PRNG) => void> = [ ]; test.skip("testRepeatGeneratingYmapTests10", (t) => { - applyRandomTests(gen, mapTransactions, 3); + applyRandomTests(t, gen, mapTransactions, 3); }); test.skip("testRepeatGeneratingYmapTests40", (t) => { - applyRandomTests(gen, mapTransactions, 40); + applyRandomTests(t, gen, mapTransactions, 40); }); test.skip("testRepeatGeneratingYmapTests42", (t) => { - applyRandomTests(gen, mapTransactions, 42); + applyRandomTests(t, gen, mapTransactions, 42); }); test.skip("testRepeatGeneratingYmapTests43", (t) => { - applyRandomTests(gen, mapTransactions, 43); + applyRandomTests(t, gen, mapTransactions, 43); }); test.skip("testRepeatGeneratingYmapTests44", (t) => { - applyRandomTests(gen, mapTransactions, 44); + applyRandomTests(t, gen, mapTransactions, 44); }); test.skip("testRepeatGeneratingYmapTests45", (t) => { - applyRandomTests(gen, mapTransactions, 45); + applyRandomTests(t, gen, mapTransactions, 45); }); test.skip("testRepeatGeneratingYmapTests46", (t) => { - applyRandomTests(gen, mapTransactions, 46); + applyRandomTests(t, gen, mapTransactions, 46); }); test.skip("testRepeatGeneratingYmapTests300", (t) => { - applyRandomTests(gen, mapTransactions, 300); + applyRandomTests(t, gen, mapTransactions, 300); }); test.skip("testRepeatGeneratingYmapTests400", (t) => { - applyRandomTests(gen, mapTransactions, 400); + applyRandomTests(t, gen, mapTransactions, 400); }); test.skip("testRepeatGeneratingYmapTests500", (t) => { - applyRandomTests(gen, mapTransactions, 500); + applyRandomTests(t, gen, mapTransactions, 500); }); test.skip("testRepeatGeneratingYmapTests600", (t) => { - applyRandomTests(gen, mapTransactions, 600); + applyRandomTests(t, gen, mapTransactions, 600); }); test.skip("testRepeatGeneratingYmapTests1000", (t) => { - applyRandomTests(gen, mapTransactions, 1000); + applyRandomTests(t, gen, mapTransactions, 1000); }); test.skip("testRepeatGeneratingYmapTests1800", (t) => { - applyRandomTests(gen, mapTransactions, 1800); + applyRandomTests(t, gen, mapTransactions, 1800); }); test.skip("testRepeatGeneratingYmapTests5000", (t) => { if (!production) return; - applyRandomTests(gen, mapTransactions, 5000); + applyRandomTests(t, gen, mapTransactions, 5000); }); test.skip("testRepeatGeneratingYmapTests10000", (t) => { if (!production) return; - applyRandomTests(gen, mapTransactions, 10000); + applyRandomTests(t, gen, mapTransactions, 10000); }); test.skip("testRepeatGeneratingYmapTests100000", (t) => { if (!production) return; - applyRandomTests(gen, mapTransactions, 100000); + applyRandomTests(t, gen, mapTransactions, 100000); }); diff --git a/y-octo-node/tests/yjs/y-text.spec.ts b/y-octo-node/tests/yjs/y-text.spec.ts index e5f74ed..5e3562d 100644 --- a/y-octo-node/tests/yjs/y-text.spec.ts +++ b/y-octo-node/tests/yjs/y-text.spec.ts @@ -1,6 +1,5 @@ -import assert, { deepEqual } from "node:assert"; import { randomInt } from "node:crypto"; -import test from "ava"; +import test, { ExecutionContext } from "ava"; import * as prng from "lib0/prng"; import * as math from "lib0/math"; @@ -1711,7 +1710,7 @@ test.skip("testBasicInsertAndDelete", (t) => { }); t.deepEqual(delta, []); - compare(users); + compare(t, users); }); test.skip("testBasicFormat", (t) => { @@ -1761,7 +1760,7 @@ test.skip("testBasicFormat", (t) => { { retain: 1 }, { retain: 1, attributes: { bold: null } }, ]); - compare(users); + compare(t, users); }); test.skip("testFalsyFormats", (t) => { @@ -1797,7 +1796,7 @@ test.skip("testFalsyFormats", (t) => { { retain: 2 }, { retain: 1, attributes: { falsy: false } }, ]); - compare(users); + compare(t, users); }); test.skip("testMultilineFormat", (t) => { @@ -2075,7 +2074,7 @@ test.skip("testInsertAndDeleteAtRandomPositions", (t) => { // expectedResult = expectedResult.slice(0, pos) + expectedResult.slice(pos + len) } } - // deepEqual(text0.toString(), expectedResult) + // t.deepEqual(text0.toString(), expectedResult) t.describe("final length", "" + text0.length); }); @@ -2219,7 +2218,7 @@ test.skip("testSplitSurrogateCharacter", (t) => { text0.insert(0, "👾"); // insert surrogate character // split surrogate, which should not lead to an encoding error text0.insert(1, "hi!"); - compare(users); + compare(t, users); } { const { users, text0 } = init(gen, { users: 2 }); @@ -2227,7 +2226,7 @@ test.skip("testSplitSurrogateCharacter", (t) => { text0.insert(0, "👾👾"); // insert surrogate character // partially delete surrogate text0.delete(1, 2); - compare(users); + compare(t, users); } { const { users, text0 } = init(gen, { users: 2 }); @@ -2235,7 +2234,7 @@ test.skip("testSplitSurrogateCharacter", (t) => { text0.insert(0, "👾👾"); // insert surrogate character // formatting will also split surrogates text0.format(1, 2, { bold: true }); - compare(users); + compare(t, users); } }); @@ -2269,9 +2268,9 @@ test.skip("testSearchMarkerBug1", (t) => { testConnector.flushAllMessages(); text1.insert(3, "d"); testConnector.flushAllMessages(); - deepEqual(text0.toString(), text1.toString()); - deepEqual(text0.toString(), "a_sda"); - compare(users); + t.deepEqual(text0.toString(), text1.toString()); + t.deepEqual(text0.toString(), "a_sda"); + compare(t, users); }); /** @@ -2335,24 +2334,22 @@ let charCounter = 0; * * @type Array */ -const textChanges: Array<(arg0: any, arg1: prng.PRNG) => void> = [ - (y: Y.Doc, gen: prng.PRNG) => { +const textChanges: Array< + (t: ExecutionContext, arg0: any, arg1: prng.PRNG) => void +> = [ + (t, y, gen) => { // insert text const ytext = y.getOrCreateText("text"); const insertPos = prng.int32(gen, 0, ytext.length); const text = charCounter++ + prng.word(gen); const prevText = ytext.toString(); ytext.insert(insertPos, text); - deepEqual( + t.deepEqual( ytext.toString(), prevText.slice(0, insertPos) + text + prevText.slice(insertPos), ); }, - /** - * @param {Y.Doc} y - * @param {prng.PRNG} gen - */ - (y: Y.Doc, gen: prng.PRNG) => { + (t, y, gen) => { // delete text const ytext = y.getOrCreateText("text"); const contentLen = ytext.toString().length; @@ -2360,7 +2357,7 @@ const textChanges: Array<(arg0: any, arg1: prng.PRNG) => void> = [ const overwrite = math.min(prng.int32(gen, 0, contentLen - insertPos), 2); const prevText = ytext.toString(); ytext.delete(insertPos, overwrite); - deepEqual( + t.deepEqual( ytext.toString(), prevText.slice(0, insertPos) + prevText.slice(insertPos + overwrite), ); @@ -2368,43 +2365,43 @@ const textChanges: Array<(arg0: any, arg1: prng.PRNG) => void> = [ ]; test.skip("testRepeatGenerateTextChanges5", (t) => { - const { users } = checkResult(applyRandomTests(gen, textChanges, 5)); + const { users } = checkResult(applyRandomTests(t, gen, textChanges, 5)); const cleanups = Y.cleanupYTextFormatting(users[0].getText("text")); t.assert(cleanups === 0); }); test.skip("testRepeatGenerateTextChanges30", (t) => { - const { users } = checkResult(applyRandomTests(gen, textChanges, 30)); + const { users } = checkResult(applyRandomTests(t, gen, textChanges, 30)); const cleanups = Y.cleanupYTextFormatting(users[0].getText("text")); t.assert(cleanups === 0); }); test.skip("testRepeatGenerateTextChanges40", (t) => { - const { users } = checkResult(applyRandomTests(gen, textChanges, 40)); + const { users } = checkResult(applyRandomTests(t, gen, textChanges, 40)); const cleanups = Y.cleanupYTextFormatting(users[0].getText("text")); t.assert(cleanups === 0); }); test.skip("testRepeatGenerateTextChanges50", (t) => { - const { users } = checkResult(applyRandomTests(gen, textChanges, 50)); + const { users } = checkResult(applyRandomTests(t, gen, textChanges, 50)); const cleanups = Y.cleanupYTextFormatting(users[0].getText("text")); t.assert(cleanups === 0); }); test.skip("testRepeatGenerateTextChanges70", (t) => { - const { users } = checkResult(applyRandomTests(gen, textChanges, 70)); + const { users } = checkResult(applyRandomTests(t, gen, textChanges, 70)); const cleanups = Y.cleanupYTextFormatting(users[0].getText("text")); t.assert(cleanups === 0); }); test.skip("testRepeatGenerateTextChanges90", (t) => { - const { users } = checkResult(applyRandomTests(gen, textChanges, 90)); + const { users } = checkResult(applyRandomTests(t, gen, textChanges, 90)); const cleanups = Y.cleanupYTextFormatting(users[0].getText("text")); t.assert(cleanups === 0); }); test.skip("testRepeatGenerateTextChanges300", (t) => { - const { users } = checkResult(applyRandomTests(gen, textChanges, 300)); + const { users } = checkResult(applyRandomTests(t, gen, textChanges, 300)); const cleanups = Y.cleanupYTextFormatting(users[0].getText("text")); t.assert(cleanups === 0); }); @@ -2564,45 +2561,45 @@ const checkResult = (result: any) => { }; test.skip("testRepeatGenerateQuillChanges1", (t) => { - const { users } = checkResult(applyRandomTests(gen, qChanges, 1)); + const { users } = checkResult(applyRandomTests(t, gen, qChanges, 1)); const cleanups = Y.cleanupYTextFormatting(users[0].getText("text")); t.assert(cleanups === 0); }); test.skip("testRepeatGenerateQuillChanges2", (t) => { - const { users } = checkResult(applyRandomTests(gen, qChanges, 2)); + const { users } = checkResult(applyRandomTests(t, gen, qChanges, 2)); const cleanups = Y.cleanupYTextFormatting(users[0].getText("text")); t.assert(cleanups === 0); }); test.skip("testRepeatGenerateQuillChanges2Repeat", (t) => { for (let i = 0; i < 1000; i++) { - const { users } = checkResult(applyRandomTests(gen, qChanges, 2)); + const { users } = checkResult(applyRandomTests(t, gen, qChanges, 2)); const cleanups = Y.cleanupYTextFormatting(users[0].getText("text")); t.assert(cleanups === 0); } }); test.skip("testRepeatGenerateQuillChanges3", (t) => { - checkResult(applyRandomTests(gen, qChanges, 3)); + checkResult(applyRandomTests(t, gen, qChanges, 3)); }); test.skip("testRepeatGenerateQuillChanges30", (t) => { - checkResult(applyRandomTests(gen, qChanges, 30)); + checkResult(applyRandomTests(t, gen, qChanges, 30)); }); test.skip("testRepeatGenerateQuillChanges40", (t) => { - checkResult(applyRandomTests(gen, qChanges, 40)); + checkResult(applyRandomTests(t, gen, qChanges, 40)); }); test.skip("testRepeatGenerateQuillChanges70", (t) => { - checkResult(applyRandomTests(gen, qChanges, 70)); + checkResult(applyRandomTests(t, gen, qChanges, 70)); }); test.skip("testRepeatGenerateQuillChanges100", (t) => { - checkResult(applyRandomTests(gen, qChanges, 100)); + checkResult(applyRandomTests(t, gen, qChanges, 100)); }); test.skip("testRepeatGenerateQuillChanges300", (t) => { - checkResult(applyRandomTests(gen, qChanges, 300)); + checkResult(applyRandomTests(t, gen, qChanges, 300)); }); From 2d18777d9e0950be93743eb2233f459089306755 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Thu, 5 Sep 2024 17:35:34 +0800 Subject: [PATCH 51/75] feat: should return undefined if value not exists --- y-octo-node/index.ts | 2 +- y-octo-node/src/array.rs | 2 +- y-octo-node/src/map.rs | 2 +- y-octo-node/src/utils/mod.rs | 9 +++++---- y-octo-node/tests/yjs/y-map.spec.ts | 6 +++--- 5 files changed, 11 insertions(+), 10 deletions(-) diff --git a/y-octo-node/index.ts b/y-octo-node/index.ts index bc2c3eb..243180d 100644 --- a/y-octo-node/index.ts +++ b/y-octo-node/index.ts @@ -312,7 +312,6 @@ export class Map { set(key: string, value: T) { if (this.ytype) { if (value instanceof Array || value instanceof Map) { - console.log("integrate", value); this.ytype.map.set(key, value.integrate(this.ytype.doc)); } else { this.ytype.map.set(key, value); @@ -321,6 +320,7 @@ export class Map { } else { this.preliminary[key] = value; } + return value; } delete(key: string): void { diff --git a/y-octo-node/src/array.rs b/y-octo-node/src/array.rs index 9e8e09d..53a280b 100644 --- a/y-octo-node/src/array.rs +++ b/y-octo-node/src/array.rs @@ -34,7 +34,7 @@ impl YArray { let value = if let Some(value) = self.array.get(index as u64) { get_mixed_y_type_from_value(env, value)? } else { - MixedYType::D(env.get_null()?.into_unknown()) + MixedYType::D(env.get_undefined()?.into_unknown()) }; Ok(value) } diff --git a/y-octo-node/src/map.rs b/y-octo-node/src/map.rs index 97fc69b..f6619f2 100644 --- a/y-octo-node/src/map.rs +++ b/y-octo-node/src/map.rs @@ -40,7 +40,7 @@ impl YMap { let value = if let Some(value) = self.map.get(&key) { get_mixed_y_type_from_value(env, value)? } else { - MixedYType::D(env.get_null()?.into_unknown()) + MixedYType::D(env.get_undefined()?.into_unknown()) }; Ok(value) diff --git a/y-octo-node/src/utils/mod.rs b/y-octo-node/src/utils/mod.rs index f61c6c3..b9c69ee 100644 --- a/y-octo-node/src/utils/mod.rs +++ b/y-octo-node/src/utils/mod.rs @@ -8,7 +8,8 @@ pub use ytype::*; pub fn get_js_unknown_from_any(env: Env, any: Any) -> Result { match any { - Any::Null | Any::Undefined => env.get_null().map(|v| v.into_unknown()), + Any::Undefined => env.get_undefined().map(|v| v.into_unknown()), + Any::Null => env.get_null().map(|v| v.into_unknown()), Any::True => env.get_boolean(true).map(|v| v.into_unknown()), Any::False => env.get_boolean(false).map(|v| v.into_unknown()), Any::Integer(number) => env.create_int32(number).map(|v| v.into_unknown()), @@ -30,7 +31,7 @@ pub fn get_js_unknown_from_any(env: Env, any: Any) -> Result { } Ok(js_object.into_unknown()) } - _ => env.get_null().map(|v| v.into_unknown()), + _ => env.get_undefined().map(|v| v.into_unknown()), } } @@ -46,7 +47,7 @@ pub fn get_js_unknown_from_value(env: Env, value: Value) -> Result { Value::Text(text) => env .create_external(YText::inner_new(text), None) .map(|o| o.into_unknown()), - _ => env.get_null().map(|v| v.into_unknown()), + _ => env.get_undefined().map(|v| v.into_unknown()), } } @@ -56,7 +57,7 @@ pub fn get_mixed_y_type_from_value(env: Env, value: Value) -> Result Value::Array(array) => Ok(YArray::inner_new(array).into()), Value::Map(map) => Ok(YMap::inner_new(map).into()), Value::Text(text) => Ok(YText::inner_new(text).into()), - _ => env.get_null().map(|v| v.into_unknown()).map(MixedYType::D), + _ => env.get_undefined().map(|v| v.into_unknown()).map(MixedYType::D), } } diff --git a/y-octo-node/tests/yjs/y-map.spec.ts b/y-octo-node/tests/yjs/y-map.spec.ts index 48a546b..0dc1e35 100644 --- a/y-octo-node/tests/yjs/y-map.spec.ts +++ b/y-octo-node/tests/yjs/y-map.spec.ts @@ -186,13 +186,13 @@ test("testGetAndSetOfMapProperty", (t) => { for (const user of users) { const u = user.getOrCreateMap("map"); t.deepEqual(u.get("stuff"), "stuffy"); - t.assert(u.get("undefined") === undefined, "undefined"); + t.is(u.get("undefined"), undefined, "undefined"); t.deepEqual(u.get("null"), null, "null"); } compare(t, users); }); -test.skip("testYmapSetsYmap", (t) => { +test("testYmapSetsYmap", (t) => { const { users, map0 } = init(gen, { users: 2 }); const map = map0.set("Map", users[0].createMap()); @@ -267,7 +267,7 @@ test("testGetAndSetAndDeleteOfMapProperty", (t) => { testConnector.flushAllMessages(); for (const user of users) { const u = user.getOrCreateMap("map"); - t.is(u.get("stuff"), null); + t.is(u.get("stuff"), undefined); } compare(t, users); }); From b81a19a772b7f7d425e46439cc0e19cd44cfd097 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Fri, 6 Sep 2024 11:47:39 +0800 Subject: [PATCH 52/75] chore: fix typing --- y-octo-node/src/doc.rs | 2 +- y-octo-node/tests/yjs/y-map.spec.ts | 4 ++-- y-octo-node/yocto.d.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/y-octo-node/src/doc.rs b/y-octo-node/src/doc.rs index ebb3e2a..e283b80 100644 --- a/y-octo-node/src/doc.rs +++ b/y-octo-node/src/doc.rs @@ -91,7 +91,7 @@ impl YDoc { Ok(ytext) } - #[napi(ts_args_type = "entries?: Iterator<[string,any]>")] + #[napi(ts_args_type = "entries?: Array<[string,any]> | Iterator<[string,any]>")] pub fn create_map(&self, env: Env, entries: Option) -> Result { let mut ymap = self.doc.create_map().map(YMap::inner_new)?; if let Some(entries) = entries { diff --git a/y-octo-node/tests/yjs/y-map.spec.ts b/y-octo-node/tests/yjs/y-map.spec.ts index 0dc1e35..a820a1a 100644 --- a/y-octo-node/tests/yjs/y-map.spec.ts +++ b/y-octo-node/tests/yjs/y-map.spec.ts @@ -37,10 +37,10 @@ test.skip("testMapEventError", (t) => { event = e; }); t.throws(() => { - t.info(event.keys); + console.info(event.keys); }); t.throws(() => { - t.info(event.keys); + console.info(event.keys); }); }); diff --git a/y-octo-node/yocto.d.ts b/y-octo-node/yocto.d.ts index b87817e..2a9c97b 100644 --- a/y-octo-node/yocto.d.ts +++ b/y-octo-node/yocto.d.ts @@ -52,7 +52,7 @@ export declare class Doc { getOrCreateMap(key: string): YMap createArray(): YArray createText(text?: string | undefined | null): YText - createMap(entries?: Iterator<[string,any]>): YMap + createMap(entries?: Array<[string,any]> | Iterator<[string,any]>): YMap applyUpdate(update: Buffer): void diff(sv?: Buffer | undefined | null): Buffer | null encodeStateAsUpdateV1(state?: Buffer | undefined | null): Buffer From 482aa2e1d84d6dadb3329924236f52912af8e215 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Fri, 6 Sep 2024 14:15:08 +0800 Subject: [PATCH 53/75] feat: item id getter of array --- y-octo-node/src/array.rs | 5 +++++ y-octo-node/tests/yjs/y-map.spec.ts | 2 +- y-octo/src/doc/types/array.rs | 5 +++++ y-octo/src/doc/types/list/mod.rs | 5 +++++ 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/y-octo-node/src/array.rs b/y-octo-node/src/array.rs index 53a280b..544e404 100644 --- a/y-octo-node/src/array.rs +++ b/y-octo-node/src/array.rs @@ -29,6 +29,11 @@ impl YArray { self.array.is_empty() } + #[napi(getter)] + pub fn item_id(&self) -> Option { + self.array.id().map(|id| YId { id }) + } + #[napi(ts_generic_types = "T = unknown", ts_return_type = "T")] pub fn get(&self, env: Env, index: i64) -> Result { let value = if let Some(value) = self.array.get(index as u64) { diff --git a/y-octo-node/tests/yjs/y-map.spec.ts b/y-octo-node/tests/yjs/y-map.spec.ts index a820a1a..8c0158e 100644 --- a/y-octo-node/tests/yjs/y-map.spec.ts +++ b/y-octo-node/tests/yjs/y-map.spec.ts @@ -206,7 +206,7 @@ test.skip("testYmapSetsYarray", (t) => { const { users, map0 } = init(gen, { users: 2 }); const array = map0.set("Array", new Y.Array()); - t.assert(array === map0.get("Array")); + t.assert(Y.compareIds(array.itemId, map0.get("Array").itemId)); array.insert(0, [1, 2, 3]); // @ts-ignore t.deepEqual(map0.toJSON(), { Array: [1, 2, 3] }); diff --git a/y-octo/src/doc/types/array.rs b/y-octo/src/doc/types/array.rs index 9d160a7..5300b21 100644 --- a/y-octo/src/doc/types/array.rs +++ b/y-octo/src/doc/types/array.rs @@ -23,6 +23,11 @@ impl Iterator for ArrayIter<'_> { } impl Array { + #[inline(always)] + pub fn id(&self) -> Option { + self._id() + } + #[inline] pub fn len(&self) -> u64 { self.content_len() diff --git a/y-octo/src/doc/types/list/mod.rs b/y-octo/src/doc/types/list/mod.rs index b885fd5..9352e83 100644 --- a/y-octo/src/doc/types/list/mod.rs +++ b/y-octo/src/doc/types/list/mod.rs @@ -56,6 +56,11 @@ impl ItemPosition { } pub(crate) trait ListType: AsInner { + #[inline(always)] + fn _id(&self) -> Option { + self.as_inner().ty().and_then(|ty| ty.item.get().map(|item| item.id)) + } + #[inline(always)] fn content_len(&self) -> u64 { self.as_inner().ty().unwrap().len From dcc90a49e6a333694e6e31ed4531c85f59ee5cf4 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Fri, 6 Sep 2024 14:58:26 +0800 Subject: [PATCH 54/75] feat: cascading value convert --- y-octo-node/src/array.rs | 8 ++++---- y-octo-node/src/map.rs | 12 ++++++++---- y-octo-node/src/utils/mod.rs | 32 +++++++++++++++++++++++++------- 3 files changed, 37 insertions(+), 15 deletions(-) diff --git a/y-octo-node/src/array.rs b/y-octo-node/src/array.rs index 544e404..3e210fe 100644 --- a/y-octo-node/src/array.rs +++ b/y-octo-node/src/array.rs @@ -62,7 +62,7 @@ impl YArray { }) .unwrap_or(self.length() - start) as usize; for value in self.array.iter().skip(start as usize).take(end) { - js_array.insert(get_js_unknown_from_value(env, value)?)?; + js_array.insert(get_js_unknown_from_value(env, value, false)?)?; } Ok(js_array) } @@ -71,7 +71,7 @@ impl YArray { pub fn map(&self, env: Env, callback: JsFunction) -> Result { let mut js_array = env.create_array(0)?; for value in self.array.iter() { - let js_value = get_js_unknown_from_value(env, value)?; + let js_value = get_js_unknown_from_value(env, value, false)?; let result = callback.call(None, &[js_value.into_unknown()])?; js_array.insert(result)?; } @@ -181,7 +181,7 @@ impl YArray { pub fn to_array(&self, env: Env) -> Result { let mut js_array = env.create_array(0)?; for value in self.array.iter() { - js_array.insert(get_js_unknown_from_value(env, value)?)?; + js_array.insert(get_js_unknown_from_value(env, value, false)?)?; } Ok(js_array) } @@ -190,7 +190,7 @@ impl YArray { pub fn to_json(&self, env: Env) -> Result { let mut js_array = env.create_array(0)?; for value in self.array.iter() { - js_array.insert(get_js_unknown_from_value(env, value)?)?; + js_array.insert(get_js_unknown_from_value(env, value, true)?)?; } Ok(js_array) } diff --git a/y-octo-node/src/map.rs b/y-octo-node/src/map.rs index f6619f2..4e6d76a 100644 --- a/y-octo-node/src/map.rs +++ b/y-octo-node/src/map.rs @@ -133,11 +133,15 @@ impl YMap { } } - #[napi] + #[napi( + js_name = "toJSON", + ts_generic_types = "T = unknown", + ts_return_type = "Record" + )] pub fn to_json(&self, env: Env) -> Result { let mut js_object = env.create_object()?; for (key, value) in self.map.iter() { - js_object.set(key, get_js_unknown_from_value(env, value))?; + js_object.set(key, get_js_unknown_from_value(env, value, true))?; } Ok(js_object) } @@ -205,7 +209,7 @@ impl Generator for YMapEntriesIterator { let mut js_array = self.env.create_array(2).ok()?; js_array.set(0, string).ok()?; js_array - .set(1, get_js_unknown_from_value(self.env, value.clone()).ok()?) + .set(1, get_js_unknown_from_value(self.env, value.clone(), false).ok()?) .ok()?; Some(js_array) } else { @@ -270,7 +274,7 @@ impl Generator for YMapValuesIterator { return None; } let ret = if let Some(value) = self.entries.get(current) { - get_js_unknown_from_value(self.env, value.clone()).ok() + get_js_unknown_from_value(self.env, value.clone(), false).ok() } else { None }; diff --git a/y-octo-node/src/utils/mod.rs b/y-octo-node/src/utils/mod.rs index b9c69ee..b3a015f 100644 --- a/y-octo-node/src/utils/mod.rs +++ b/y-octo-node/src/utils/mod.rs @@ -35,15 +35,33 @@ pub fn get_js_unknown_from_any(env: Env, any: Any) -> Result { } } -pub fn get_js_unknown_from_value(env: Env, value: Value) -> Result { +pub fn get_js_unknown_from_value(env: Env, value: Value, cascading: bool) -> Result { match value { Value::Any(any) => get_js_unknown_from_any(env, any), - Value::Array(array) => env - .create_external(YArray::inner_new(array), None) - .map(|o| o.into_unknown()), - Value::Map(map) => env - .create_external(YMap::inner_new(map), None) - .map(|o| o.into_unknown()), + Value::Array(array) => { + if cascading { + let mut js_array = env.create_array_with_length(array.len() as usize)?; + for (i, value) in array.iter().enumerate() { + js_array.set_element(i as u32, get_js_unknown_from_value(env, value, cascading)?)?; + } + Ok(js_array.into_unknown()) + } else { + env.create_external(YArray::inner_new(array), None) + .map(|o| o.into_unknown()) + } + } + Value::Map(map) => { + if cascading { + let mut js_object = env.create_object()?; + for (key, value) in map.iter() { + js_object.set_named_property(key, get_js_unknown_from_value(env, value, cascading)?)?; + } + Ok(js_object.into_unknown()) + } else { + env.create_external(YMap::inner_new(map), None) + .map(|o| o.into_unknown()) + } + } Value::Text(text) => env .create_external(YText::inner_new(text), None) .map(|o| o.into_unknown()), From b83841fa58a9d51edd68226d7ca25e5f0d2cb6f3 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Fri, 6 Sep 2024 15:48:25 +0800 Subject: [PATCH 55/75] feat: rename toJSON --- y-octo-node/index.ts | 8 ++++++-- y-octo-node/tests/yjs/testHelper.ts | 2 +- y-octo-node/yocto.d.ts | 3 ++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/y-octo-node/index.ts b/y-octo-node/index.ts index 243180d..76bb2a0 100644 --- a/y-octo-node/index.ts +++ b/y-octo-node/index.ts @@ -116,6 +116,10 @@ export class Array { private ytype?: { doc: Doc; array: Y.YArray }; private preliminary: any[] = []; + get itemId() { + return this.ytype?.array.itemId; + } + static from(items: T[]): Array { return new Array(items); } @@ -341,9 +345,9 @@ export class Map { } } - toJson(): Record { + toJSON(): Record { return this.ytype - ? this.ytype.map.toJson() + ? this.ytype.map.toJSON() : JSON.parse(JSON.stringify(this.preliminary)); } diff --git a/y-octo-node/tests/yjs/testHelper.ts b/y-octo-node/tests/yjs/testHelper.ts index 5775f4c..7e673c8 100644 --- a/y-octo-node/tests/yjs/testHelper.ts +++ b/y-octo-node/tests/yjs/testHelper.ts @@ -260,7 +260,7 @@ export const compare = (t: ExecutionContext, users: TestYOctoInstance[]) => { // }); // users.push(...mergedDocs); const userArrayValues = users.map((u) => u.getArray("array").toJSON()); - const userMapValues = users.map((u) => u.getMap("map").toJson()); + const userMapValues = users.map((u) => u.getMap("map").toJSON()); // const userXmlValues = users.map( // (u: { // get: ( diff --git a/y-octo-node/yocto.d.ts b/y-octo-node/yocto.d.ts index 2a9c97b..1b26332 100644 --- a/y-octo-node/yocto.d.ts +++ b/y-octo-node/yocto.d.ts @@ -17,6 +17,7 @@ export declare function isAbstractType(unknown: unknown): boolean export declare class YArray { get length(): number get isEmpty(): boolean + get itemId(): YId | null get(index: number): T slice(start: number, end?: number | undefined | null): Array map(callback: (...args: any[]) => any): Array @@ -70,7 +71,7 @@ export declare class YMap { set | null | undefined>(key: string, value: T): T delete(key: string): void clear(): void - toJson(): object + toJSON(): Record entries(): YMapEntriesIterator keys(): YMapKeyIterator values(): YMapValuesIterator From 02e7006c660828a26d67c4c59fcfeac19837dfdd Mon Sep 17 00:00:00 2001 From: DarkSky Date: Fri, 6 Sep 2024 16:20:10 +0800 Subject: [PATCH 56/75] feat: improve abstract type check --- y-octo-node/src/function.rs | 6 +++--- y-octo-node/yocto.d.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/y-octo-node/src/function.rs b/y-octo-node/src/function.rs index 66a67ce..dd62cd9 100644 --- a/y-octo-node/src/function.rs +++ b/y-octo-node/src/function.rs @@ -71,7 +71,7 @@ pub fn merge_updates(updates: Vec) -> Result { Ok(merge_updates_v1(updates)?.encode_v1()?.into()) } -#[napi] -pub fn is_abstract_type(env: Env, unknown: JsUnknown) -> Result { - Ok(YArray::instance_of(env, &unknown)? || YMap::instance_of(env, &unknown)? || YText::instance_of(env, &unknown)?) +#[napi(ts_args_type = "obj?: any")] +pub fn is_abstract_type(unknown: MixedRefYType) -> bool { + matches!(unknown, MixedRefYType::A(_) | MixedRefYType::B(_) | MixedRefYType::C(_)) } diff --git a/y-octo-node/yocto.d.ts b/y-octo-node/yocto.d.ts index 1b26332..15016ca 100644 --- a/y-octo-node/yocto.d.ts +++ b/y-octo-node/yocto.d.ts @@ -13,7 +13,7 @@ export declare function snapshot(doc: Doc): YSnapshot export declare function encodeSnapshot(snapshot: YSnapshot): Buffer export declare function applyUpdate(doc: Doc, update: Buffer): void export declare function mergeUpdates(updates: Array): Buffer -export declare function isAbstractType(unknown: unknown): boolean +export declare function isAbstractType(obj?: any): boolean export declare class YArray { get length(): number get isEmpty(): boolean From 55018ac5eda3344908942c5a392322b62e1aa9e5 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Fri, 6 Sep 2024 17:35:04 +0800 Subject: [PATCH 57/75] feat: merge MixedYType converter --- y-octo-node/src/array.rs | 8 ++++---- y-octo-node/src/lib.rs | 4 ++-- y-octo-node/src/map.rs | 10 +++++----- y-octo-node/src/text.rs | 6 ------ y-octo-node/src/utils/mod.rs | 32 ++++++++++---------------------- y-octo-node/src/utils/ytype.rs | 29 +++++++++++++++++++++++++++++ 6 files changed, 50 insertions(+), 39 deletions(-) diff --git a/y-octo-node/src/array.rs b/y-octo-node/src/array.rs index 3e210fe..96b9bcb 100644 --- a/y-octo-node/src/array.rs +++ b/y-octo-node/src/array.rs @@ -37,7 +37,7 @@ impl YArray { #[napi(ts_generic_types = "T = unknown", ts_return_type = "T")] pub fn get(&self, env: Env, index: i64) -> Result { let value = if let Some(value) = self.array.get(index as u64) { - get_mixed_y_type_from_value(env, value)? + get_mixed_y_type_from_value(env, value, false)? } else { MixedYType::D(env.get_undefined()?.into_unknown()) }; @@ -62,7 +62,7 @@ impl YArray { }) .unwrap_or(self.length() - start) as usize; for value in self.array.iter().skip(start as usize).take(end) { - js_array.insert(get_js_unknown_from_value(env, value, false)?)?; + js_array.insert(get_mixed_y_type_from_value(env, value, false)?)?; } Ok(js_array) } @@ -181,7 +181,7 @@ impl YArray { pub fn to_array(&self, env: Env) -> Result { let mut js_array = env.create_array(0)?; for value in self.array.iter() { - js_array.insert(get_js_unknown_from_value(env, value, false)?)?; + js_array.insert(get_mixed_y_type_from_value(env, value, false)?)?; } Ok(js_array) } @@ -190,7 +190,7 @@ impl YArray { pub fn to_json(&self, env: Env) -> Result { let mut js_array = env.create_array(0)?; for value in self.array.iter() { - js_array.insert(get_js_unknown_from_value(env, value, true)?)?; + js_array.insert(get_mixed_y_type_from_value(env, value, true)?)?; } Ok(js_array) } diff --git a/y-octo-node/src/lib.rs b/y-octo-node/src/lib.rs index dec8242..cba8ac2 100644 --- a/y-octo-node/src/lib.rs +++ b/y-octo-node/src/lib.rs @@ -20,6 +20,6 @@ pub use protocol::*; pub use text::*; pub use types::*; use utils::{ - get_any_from_js_object, get_any_from_js_unknown, get_js_unknown_from_any, get_js_unknown_from_value, - get_mixed_y_type_from_value, MixedRefYType, MixedYType, + get_any_from_js_object, get_any_from_js_unknown, get_js_unknown_from_any, get_mixed_y_type_from_value, + MixedRefYType, MixedYType, }; diff --git a/y-octo-node/src/map.rs b/y-octo-node/src/map.rs index 4e6d76a..51f89aa 100644 --- a/y-octo-node/src/map.rs +++ b/y-octo-node/src/map.rs @@ -38,7 +38,7 @@ impl YMap { #[napi(ts_generic_types = "T = unknown", ts_return_type = "T")] pub fn get(&self, env: Env, key: String) -> Result { let value = if let Some(value) = self.map.get(&key) { - get_mixed_y_type_from_value(env, value)? + get_mixed_y_type_from_value(env, value, false)? } else { MixedYType::D(env.get_undefined()?.into_unknown()) }; @@ -141,7 +141,7 @@ impl YMap { pub fn to_json(&self, env: Env) -> Result { let mut js_object = env.create_object()?; for (key, value) in self.map.iter() { - js_object.set(key, get_js_unknown_from_value(env, value, true))?; + js_object.set(key, get_mixed_y_type_from_value(env, value, true))?; } Ok(js_object) } @@ -209,7 +209,7 @@ impl Generator for YMapEntriesIterator { let mut js_array = self.env.create_array(2).ok()?; js_array.set(0, string).ok()?; js_array - .set(1, get_js_unknown_from_value(self.env, value.clone(), false).ok()?) + .set(1, get_mixed_y_type_from_value(self.env, value.clone(), false).ok()?) .ok()?; Some(js_array) } else { @@ -262,7 +262,7 @@ pub struct YMapValuesIterator { #[napi] impl Generator for YMapValuesIterator { - type Yield = JsUnknown; + type Yield = MixedYType; type Next = Option; @@ -274,7 +274,7 @@ impl Generator for YMapValuesIterator { return None; } let ret = if let Some(value) = self.entries.get(current) { - get_js_unknown_from_value(self.env, value.clone(), false).ok() + get_mixed_y_type_from_value(self.env, value.clone(), false).ok() } else { None }; diff --git a/y-octo-node/src/text.rs b/y-octo-node/src/text.rs index 711928a..8a35a79 100644 --- a/y-octo-node/src/text.rs +++ b/y-octo-node/src/text.rs @@ -11,12 +11,6 @@ pub struct YText { #[napi] impl YText { - #[allow(clippy::new_without_default)] - #[napi(constructor)] - pub fn new() -> Self { - unimplemented!() - } - pub(crate) fn inner_new(text: Text) -> Self { Self { text } } diff --git a/y-octo-node/src/utils/mod.rs b/y-octo-node/src/utils/mod.rs index b3a015f..369d996 100644 --- a/y-octo-node/src/utils/mod.rs +++ b/y-octo-node/src/utils/mod.rs @@ -35,45 +35,33 @@ pub fn get_js_unknown_from_any(env: Env, any: Any) -> Result { } } -pub fn get_js_unknown_from_value(env: Env, value: Value, cascading: bool) -> Result { +pub fn get_mixed_y_type_from_value(env: Env, value: Value, cascading: bool) -> Result { match value { - Value::Any(any) => get_js_unknown_from_any(env, any), + Value::Any(any) => get_js_unknown_from_any(env, any).map(MixedYType::D), Value::Array(array) => { if cascading { let mut js_array = env.create_array_with_length(array.len() as usize)?; for (i, value) in array.iter().enumerate() { - js_array.set_element(i as u32, get_js_unknown_from_value(env, value, cascading)?)?; + let value = get_mixed_y_type_from_value(env, value, cascading)?; + let instance = MixedYTypeClass::try_from((env.clone(), value))?; + js_array.set_element(i as u32, instance.as_unknown(env.clone()))?; } - Ok(js_array.into_unknown()) + Ok(MixedYType::D(js_array.into_unknown())) } else { - env.create_external(YArray::inner_new(array), None) - .map(|o| o.into_unknown()) + Ok(YArray::inner_new(array).into()) } } Value::Map(map) => { if cascading { let mut js_object = env.create_object()?; for (key, value) in map.iter() { - js_object.set_named_property(key, get_js_unknown_from_value(env, value, cascading)?)?; + js_object.set_named_property(key, get_mixed_y_type_from_value(env, value, cascading)?)?; } - Ok(js_object.into_unknown()) + Ok(MixedYType::D(js_object.into_unknown())) } else { - env.create_external(YMap::inner_new(map), None) - .map(|o| o.into_unknown()) + Ok(YMap::inner_new(map).into()) } } - Value::Text(text) => env - .create_external(YText::inner_new(text), None) - .map(|o| o.into_unknown()), - _ => env.get_undefined().map(|v| v.into_unknown()), - } -} - -pub fn get_mixed_y_type_from_value(env: Env, value: Value) -> Result { - match value { - Value::Any(any) => get_js_unknown_from_any(env, any).map(MixedYType::D), - Value::Array(array) => Ok(YArray::inner_new(array).into()), - Value::Map(map) => Ok(YMap::inner_new(map).into()), Value::Text(text) => Ok(YText::inner_new(text).into()), _ => env.get_undefined().map(|v| v.into_unknown()).map(MixedYType::D), } diff --git a/y-octo-node/src/utils/ytype.rs b/y-octo-node/src/utils/ytype.rs index efbeb4e..0ddf16f 100644 --- a/y-octo-node/src/utils/ytype.rs +++ b/y-octo-node/src/utils/ytype.rs @@ -1,4 +1,6 @@ use super::*; +use napi::bindgen_prelude::ClassInstance; +use std::ops::Deref; pub type MixedRefYType<'a> = Either4<&'a YArray, &'a YMap, &'a YText, JsUnknown>; @@ -22,3 +24,30 @@ impl_into_mixed_y_type!(YMap, B); impl_into_mixed_y_type!(&YMap, B); impl_into_mixed_y_type!(YText, C); impl_into_mixed_y_type!(&YText, C); + +type MixedYTypeInstance = Either4, ClassInstance, ClassInstance, JsUnknown>; + +pub struct MixedYTypeClass(pub MixedYTypeInstance); + +impl TryFrom<(Env, MixedYType)> for MixedYTypeClass { + type Error = napi::Error; + + fn try_from(params: (Env, MixedYType)) -> Result { + let (env, instance) = params; + let ret = match instance { + Either4::A(i) => MixedYTypeInstance::A(i.into_instance(env)?), + Either4::B(i) => MixedYTypeInstance::B(i.into_instance(env)?), + Either4::C(i) => MixedYTypeInstance::C(i.into_instance(env)?), + Either4::D(i) => MixedYTypeInstance::D(i), + }; + Ok(Self(ret)) + } +} + +impl Deref for MixedYTypeClass { + type Target = MixedYTypeInstance; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} From affb613eb9663be78a0c528bebbd12f8a2598cfd Mon Sep 17 00:00:00 2001 From: DarkSky Date: Mon, 9 Sep 2024 11:24:43 +0800 Subject: [PATCH 58/75] feat: enable more test case --- y-octo-node/tests/yjs/y-map.spec.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/y-octo-node/tests/yjs/y-map.spec.ts b/y-octo-node/tests/yjs/y-map.spec.ts index 8c0158e..7c6d336 100644 --- a/y-octo-node/tests/yjs/y-map.spec.ts +++ b/y-octo-node/tests/yjs/y-map.spec.ts @@ -202,7 +202,7 @@ test("testYmapSetsYmap", (t) => { compare(t, users); }); -test.skip("testYmapSetsYarray", (t) => { +test("testYmapSetsYarray", (t) => { const { users, map0 } = init(gen, { users: 2 }); const array = map0.set("Array", new Y.Array()); @@ -213,7 +213,7 @@ test.skip("testYmapSetsYarray", (t) => { compare(t, users); }); -test.skip("testGetAndSetOfMapPropertySyncs", (t) => { +test("testGetAndSetOfMapPropertySyncs", (t) => { const { testConnector, users, map0 } = init(gen, { users: 2 }); map0.set("stuff", "stuffy"); @@ -272,7 +272,7 @@ test("testGetAndSetAndDeleteOfMapProperty", (t) => { compare(t, users); }); -test.skip("testSetAndClearOfMapProperties", (t) => { +test("testSetAndClearOfMapProperties", (t) => { const { testConnector, users, map0 } = init(gen, { users: 1 }); map0.set("stuff", "c0"); @@ -281,8 +281,8 @@ test.skip("testSetAndClearOfMapProperties", (t) => { testConnector.flushAllMessages(); for (const user of users) { const u = user.getOrCreateMap("map"); - t.is(u.get("stuff"), null); - t.is(u.get("otherstuff"), null); + t.is(u.get("stuff"), undefined); + t.is(u.get("otherstuff"), undefined); t.is(u.size, 0, `map size after clear is ${u.size}, expected 0`); } compare(t, users); From 5643c3c531b8d257268bfbee14ea251f604f7768 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Mon, 9 Sep 2024 14:31:03 +0800 Subject: [PATCH 59/75] feat: improve map callback typing --- y-octo-node/src/array.rs | 15 +++++++++------ y-octo-node/yocto.d.ts | 5 ++--- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/y-octo-node/src/array.rs b/y-octo-node/src/array.rs index 96b9bcb..a24e794 100644 --- a/y-octo-node/src/array.rs +++ b/y-octo-node/src/array.rs @@ -1,5 +1,5 @@ use napi::{ - bindgen_prelude::{Array as JsArray, FromNapiValue}, + bindgen_prelude::{Array as JsArray, FromNapiValue, Function}, iterator::Generator, Env, JsFunction, JsUnknown, ValueType, }; @@ -67,13 +67,16 @@ impl YArray { Ok(js_array) } - #[napi(ts_generic_types = "T = unknown", ts_return_type = "Array")] - pub fn map(&self, env: Env, callback: JsFunction) -> Result { + #[napi( + ts_args_type = "value: YArray | YMap | YText | boolean | number | string | Record | null | undefined", + ts_generic_types = "T = unknown", + ts_return_type = "Array" + )] + pub fn map(&self, env: Env, callback: Function) -> Result { let mut js_array = env.create_array(0)?; for value in self.array.iter() { - let js_value = get_js_unknown_from_value(env, value, false)?; - let result = callback.call(None, &[js_value.into_unknown()])?; - js_array.insert(result)?; + let value = get_mixed_y_type_from_value(env, value, false)?; + js_array.insert(callback.call(value)?)?; } Ok(js_array) } diff --git a/y-octo-node/yocto.d.ts b/y-octo-node/yocto.d.ts index 15016ca..6ce8d9a 100644 --- a/y-octo-node/yocto.d.ts +++ b/y-octo-node/yocto.d.ts @@ -20,7 +20,7 @@ export declare class YArray { get itemId(): YId | null get(index: number): T slice(start: number, end?: number | undefined | null): Array - map(callback: (...args: any[]) => any): Array + map(value: YArray | YMap | YText | boolean | number | string | Record | null | undefined): Array insert(index: number, value: YArray | YMap | YText | boolean | number | string | Record | null | undefined): void push(value: YArray | YMap | YText | boolean | number | string | Record | null | undefined): void unshift(value: YArray | YMap | YText | boolean | number | string | Record | null | undefined): void @@ -85,7 +85,7 @@ export declare class YMapKeyIterator { [Symbol.iterator](): Iterator } export declare class YMapValuesIterator { - [Symbol.iterator](): Iterator + [Symbol.iterator](): Iterator } export declare class YProtocol { constructor(doc: Doc) @@ -93,7 +93,6 @@ export declare class YProtocol { applySyncStep(buffer: Buffer): Buffer | null } export declare class YText { - constructor() get len(): number get isEmpty(): boolean insert(index: number, str: string): void From 570b0e5af8bd0bcd2df789006ff55343d3eacc72 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Mon, 9 Sep 2024 15:48:37 +0800 Subject: [PATCH 60/75] chore: cleanup codes --- y-octo-node/src/utils/mod.rs | 4 ++-- y-octo-node/src/utils/ytype.rs | 24 ++++++++++-------------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/y-octo-node/src/utils/mod.rs b/y-octo-node/src/utils/mod.rs index 369d996..6372a08 100644 --- a/y-octo-node/src/utils/mod.rs +++ b/y-octo-node/src/utils/mod.rs @@ -43,8 +43,8 @@ pub fn get_mixed_y_type_from_value(env: Env, value: Value, cascading: bool) -> R let mut js_array = env.create_array_with_length(array.len() as usize)?; for (i, value) in array.iter().enumerate() { let value = get_mixed_y_type_from_value(env, value, cascading)?; - let instance = MixedYTypeClass::try_from((env.clone(), value))?; - js_array.set_element(i as u32, instance.as_unknown(env.clone()))?; + let instance = MixedClassYType::try_from((env, value))?; + js_array.set_element(i as u32, instance.into_inner())?; } Ok(MixedYType::D(js_array.into_unknown())) } else { diff --git a/y-octo-node/src/utils/ytype.rs b/y-octo-node/src/utils/ytype.rs index 0ddf16f..d86d9b0 100644 --- a/y-octo-node/src/utils/ytype.rs +++ b/y-octo-node/src/utils/ytype.rs @@ -25,29 +25,25 @@ impl_into_mixed_y_type!(&YMap, B); impl_into_mixed_y_type!(YText, C); impl_into_mixed_y_type!(&YText, C); -type MixedYTypeInstance = Either4, ClassInstance, ClassInstance, JsUnknown>; +pub struct MixedClassYType(pub JsUnknown); -pub struct MixedYTypeClass(pub MixedYTypeInstance); - -impl TryFrom<(Env, MixedYType)> for MixedYTypeClass { +impl TryFrom<(Env, MixedYType)> for MixedClassYType { type Error = napi::Error; fn try_from(params: (Env, MixedYType)) -> Result { let (env, instance) = params; let ret = match instance { - Either4::A(i) => MixedYTypeInstance::A(i.into_instance(env)?), - Either4::B(i) => MixedYTypeInstance::B(i.into_instance(env)?), - Either4::C(i) => MixedYTypeInstance::C(i.into_instance(env)?), - Either4::D(i) => MixedYTypeInstance::D(i), + Either4::A(i) => Either4::A(i.into_instance(env)?), + Either4::B(i) => Either4::B(i.into_instance(env)?), + Either4::C(i) => Either4::C(i.into_instance(env)?), + Either4::D(i) => Either4::D(i), }; - Ok(Self(ret)) + Ok(Self(ret.as_unknown(env))) } } -impl Deref for MixedYTypeClass { - type Target = MixedYTypeInstance; - - fn deref(&self) -> &Self::Target { - &self.0 +impl MixedClassYType { + pub fn into_inner(self) -> JsUnknown { + self.0 } } From 4edaef14d3527282c36d26bff9946cbb3eaf776c Mon Sep 17 00:00:00 2001 From: DarkSky Date: Mon, 9 Sep 2024 16:02:25 +0800 Subject: [PATCH 61/75] chore: cleanup import --- y-octo-node/src/utils/ytype.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/y-octo-node/src/utils/ytype.rs b/y-octo-node/src/utils/ytype.rs index d86d9b0..f109556 100644 --- a/y-octo-node/src/utils/ytype.rs +++ b/y-octo-node/src/utils/ytype.rs @@ -1,6 +1,4 @@ use super::*; -use napi::bindgen_prelude::ClassInstance; -use std::ops::Deref; pub type MixedRefYType<'a> = Either4<&'a YArray, &'a YMap, &'a YText, JsUnknown>; From 7ea21972aae4d26f7c812f491bba18470949cd6f Mon Sep 17 00:00:00 2001 From: DarkSky Date: Mon, 9 Sep 2024 17:00:48 +0800 Subject: [PATCH 62/75] feat: reorganize utils --- y-octo-node/src/utils/from_js.rs | 57 ++++++++++++++ y-octo-node/src/utils/mod.rs | 123 ++----------------------------- y-octo-node/src/utils/to_js.rs | 64 ++++++++++++++++ y-octo-node/src/utils/ytype.rs | 2 + 4 files changed, 129 insertions(+), 117 deletions(-) create mode 100644 y-octo-node/src/utils/from_js.rs create mode 100644 y-octo-node/src/utils/to_js.rs diff --git a/y-octo-node/src/utils/from_js.rs b/y-octo-node/src/utils/from_js.rs new file mode 100644 index 0000000..18ab5e8 --- /dev/null +++ b/y-octo-node/src/utils/from_js.rs @@ -0,0 +1,57 @@ +use napi::{Error, JsObject, Status, ValueType}; +use y_octo::{AHashMap, Any, HashMapExt}; + +use super::*; + +pub fn get_any_from_js_object(object: JsObject) -> Result { + if let Ok(length) = object.get_array_length() { + let mut array = Vec::with_capacity(length as usize); + for i in 0..length { + if let Ok(value) = object.get_element::(i) { + array.push(get_any_from_js_unknown(value)?); + } + } + Ok(Any::Array(array)) + } else { + let mut map = AHashMap::new(); + let keys = object.get_property_names()?; + if let Ok(length) = keys.get_array_length() { + for i in 0..length { + if let Ok((obj, key)) = keys.get_element::(i).and_then(|o| { + o.coerce_to_string() + .and_then(|obj| obj.into_utf8().and_then(|s| s.as_str().map(|s| (obj, s.to_string())))) + }) { + if let Ok(value) = object.get_property::<_, JsUnknown>(obj) { + map.insert(key, get_any_from_js_unknown(value)?); + } + } + } + } + Ok(Any::Object(map)) + } +} + +pub fn get_any_from_js_unknown(js_unknown: JsUnknown) -> Result { + match js_unknown.get_type()? { + ValueType::Undefined => Ok(Any::Undefined), + ValueType::Null => Ok(Any::Null), + ValueType::Boolean => Ok(js_unknown.coerce_to_bool().and_then(|v| v.get_value())?.into()), + ValueType::Number => Ok(js_unknown + .coerce_to_number() + .and_then(|v| v.get_double()) + .map(|v| v.into())?), + ValueType::String => Ok(js_unknown + .coerce_to_string() + .and_then(|v| v.into_utf8()) + .and_then(|s| s.as_str().map(|s| s.to_string()))? + .into()), + ValueType::Object => { + if let Ok(object) = js_unknown.coerce_to_object() { + get_any_from_js_object(object) + } else { + Err(Error::new(Status::InvalidArg, "Failed to coerce value to object")) + } + } + _ => Err(Error::new(Status::InvalidArg, "Failed to coerce value to any")), + } +} diff --git a/y-octo-node/src/utils/mod.rs b/y-octo-node/src/utils/mod.rs index 6372a08..f79cb1a 100644 --- a/y-octo-node/src/utils/mod.rs +++ b/y-octo-node/src/utils/mod.rs @@ -1,121 +1,10 @@ +mod from_js; +mod to_js; mod ytype; -use super::*; -use napi::{bindgen_prelude::Either4, Env, Error, JsObject, JsUnknown, Result, Status, ValueType}; -use y_octo::{AHashMap, Any, HashMapExt, Value}; - +pub use from_js::*; +use napi::{Env, JsUnknown, Result}; +pub use to_js::*; pub use ytype::*; -pub fn get_js_unknown_from_any(env: Env, any: Any) -> Result { - match any { - Any::Undefined => env.get_undefined().map(|v| v.into_unknown()), - Any::Null => env.get_null().map(|v| v.into_unknown()), - Any::True => env.get_boolean(true).map(|v| v.into_unknown()), - Any::False => env.get_boolean(false).map(|v| v.into_unknown()), - Any::Integer(number) => env.create_int32(number).map(|v| v.into_unknown()), - Any::BigInt64(number) => env.create_int64(number).map(|v| v.into_unknown()), - Any::Float32(number) => env.create_double(number.0 as f64).map(|v| v.into_unknown()), - Any::Float64(number) => env.create_double(number.0).map(|v| v.into_unknown()), - Any::String(string) => env.create_string(string.as_str()).map(|v| v.into_unknown()), - Any::Array(array) => { - let mut js_array = env.create_array_with_length(array.len())?; - for (i, value) in array.into_iter().enumerate() { - js_array.set_element(i as u32, get_js_unknown_from_any(env, value)?)?; - } - Ok(js_array.into_unknown()) - } - Any::Object(object) => { - let mut js_object = env.create_object()?; - for (key, value) in object.into_iter() { - js_object.set_named_property(&key, get_js_unknown_from_any(env, value)?)?; - } - Ok(js_object.into_unknown()) - } - _ => env.get_undefined().map(|v| v.into_unknown()), - } -} - -pub fn get_mixed_y_type_from_value(env: Env, value: Value, cascading: bool) -> Result { - match value { - Value::Any(any) => get_js_unknown_from_any(env, any).map(MixedYType::D), - Value::Array(array) => { - if cascading { - let mut js_array = env.create_array_with_length(array.len() as usize)?; - for (i, value) in array.iter().enumerate() { - let value = get_mixed_y_type_from_value(env, value, cascading)?; - let instance = MixedClassYType::try_from((env, value))?; - js_array.set_element(i as u32, instance.into_inner())?; - } - Ok(MixedYType::D(js_array.into_unknown())) - } else { - Ok(YArray::inner_new(array).into()) - } - } - Value::Map(map) => { - if cascading { - let mut js_object = env.create_object()?; - for (key, value) in map.iter() { - js_object.set_named_property(key, get_mixed_y_type_from_value(env, value, cascading)?)?; - } - Ok(MixedYType::D(js_object.into_unknown())) - } else { - Ok(YMap::inner_new(map).into()) - } - } - Value::Text(text) => Ok(YText::inner_new(text).into()), - _ => env.get_undefined().map(|v| v.into_unknown()).map(MixedYType::D), - } -} - -pub fn get_any_from_js_object(object: JsObject) -> Result { - if let Ok(length) = object.get_array_length() { - let mut array = Vec::with_capacity(length as usize); - for i in 0..length { - if let Ok(value) = object.get_element::(i) { - array.push(get_any_from_js_unknown(value)?); - } - } - Ok(Any::Array(array)) - } else { - let mut map = AHashMap::new(); - let keys = object.get_property_names()?; - if let Ok(length) = keys.get_array_length() { - for i in 0..length { - if let Ok((obj, key)) = keys.get_element::(i).and_then(|o| { - o.coerce_to_string() - .and_then(|obj| obj.into_utf8().and_then(|s| s.as_str().map(|s| (obj, s.to_string())))) - }) { - if let Ok(value) = object.get_property::<_, JsUnknown>(obj) { - map.insert(key, get_any_from_js_unknown(value)?); - } - } - } - } - Ok(Any::Object(map)) - } -} - -pub fn get_any_from_js_unknown(js_unknown: JsUnknown) -> Result { - match js_unknown.get_type()? { - ValueType::Undefined => Ok(Any::Undefined), - ValueType::Null => Ok(Any::Null), - ValueType::Boolean => Ok(js_unknown.coerce_to_bool().and_then(|v| v.get_value())?.into()), - ValueType::Number => Ok(js_unknown - .coerce_to_number() - .and_then(|v| v.get_double()) - .map(|v| v.into())?), - ValueType::String => Ok(js_unknown - .coerce_to_string() - .and_then(|v| v.into_utf8()) - .and_then(|s| s.as_str().map(|s| s.to_string()))? - .into()), - ValueType::Object => { - if let Ok(object) = js_unknown.coerce_to_object() { - get_any_from_js_object(object) - } else { - Err(Error::new(Status::InvalidArg, "Failed to coerce value to object")) - } - } - _ => Err(Error::new(Status::InvalidArg, "Failed to coerce value to any")), - } -} +use super::*; diff --git a/y-octo-node/src/utils/to_js.rs b/y-octo-node/src/utils/to_js.rs new file mode 100644 index 0000000..7a954f3 --- /dev/null +++ b/y-octo-node/src/utils/to_js.rs @@ -0,0 +1,64 @@ +use y_octo::{Any, Value}; + +use super::*; + +pub fn get_js_unknown_from_any(env: Env, any: Any) -> Result { + match any { + Any::Undefined => env.get_undefined().map(|v| v.into_unknown()), + Any::Null => env.get_null().map(|v| v.into_unknown()), + Any::True => env.get_boolean(true).map(|v| v.into_unknown()), + Any::False => env.get_boolean(false).map(|v| v.into_unknown()), + Any::Integer(number) => env.create_int32(number).map(|v| v.into_unknown()), + Any::BigInt64(number) => env.create_int64(number).map(|v| v.into_unknown()), + Any::Float32(number) => env.create_double(number.0 as f64).map(|v| v.into_unknown()), + Any::Float64(number) => env.create_double(number.0).map(|v| v.into_unknown()), + Any::String(string) => env.create_string(string.as_str()).map(|v| v.into_unknown()), + Any::Array(array) => { + let mut js_array = env.create_array_with_length(array.len())?; + for (i, value) in array.into_iter().enumerate() { + js_array.set_element(i as u32, get_js_unknown_from_any(env, value)?)?; + } + Ok(js_array.into_unknown()) + } + Any::Object(object) => { + let mut js_object = env.create_object()?; + for (key, value) in object.into_iter() { + js_object.set_named_property(&key, get_js_unknown_from_any(env, value)?)?; + } + Ok(js_object.into_unknown()) + } + _ => env.get_undefined().map(|v| v.into_unknown()), + } +} + +pub fn get_mixed_y_type_from_value(env: Env, value: Value, cascading: bool) -> Result { + match value { + Value::Any(any) => get_js_unknown_from_any(env, any).map(MixedYType::D), + Value::Array(array) => { + if cascading { + let mut js_array = env.create_array_with_length(array.len() as usize)?; + for (i, value) in array.iter().enumerate() { + let value = get_mixed_y_type_from_value(env, value, cascading)?; + let instance = MixedClassYType::try_from((env, value))?; + js_array.set_element(i as u32, instance.into_inner())?; + } + Ok(MixedYType::D(js_array.into_unknown())) + } else { + Ok(YArray::inner_new(array).into()) + } + } + Value::Map(map) => { + if cascading { + let mut js_object = env.create_object()?; + for (key, value) in map.iter() { + js_object.set_named_property(key, get_mixed_y_type_from_value(env, value, cascading)?)?; + } + Ok(MixedYType::D(js_object.into_unknown())) + } else { + Ok(YMap::inner_new(map).into()) + } + } + Value::Text(text) => Ok(YText::inner_new(text).into()), + _ => env.get_undefined().map(|v| v.into_unknown()).map(MixedYType::D), + } +} diff --git a/y-octo-node/src/utils/ytype.rs b/y-octo-node/src/utils/ytype.rs index f109556..2521e21 100644 --- a/y-octo-node/src/utils/ytype.rs +++ b/y-octo-node/src/utils/ytype.rs @@ -1,3 +1,5 @@ +use napi::bindgen_prelude::Either4; + use super::*; pub type MixedRefYType<'a> = Either4<&'a YArray, &'a YMap, &'a YText, JsUnknown>; From 856669dbb976df2c43935f2cbdee9ae072c655ad Mon Sep 17 00:00:00 2001 From: DarkSky Date: Mon, 9 Sep 2024 17:26:36 +0800 Subject: [PATCH 63/75] chore: cleanup codes --- y-octo-node/Cargo.toml | 2 +- y-octo-node/src/array.rs | 3 ++- y-octo-node/src/awareness.rs | 6 +++--- y-octo-node/src/function.rs | 2 +- y-octo-node/src/lib.rs | 5 +---- y-octo-node/src/map.rs | 4 ++-- y-octo-node/src/protocol.rs | 11 ++++++----- y-octo/src/doc/awareness.rs | 2 +- y-octo/src/doc/codec/item_flag.rs | 30 +++++++++++++++--------------- y-octo/src/doc/publisher.rs | 2 +- y-octo/src/doc/types/text.rs | 2 +- 11 files changed, 34 insertions(+), 35 deletions(-) diff --git a/y-octo-node/Cargo.toml b/y-octo-node/Cargo.toml index a6ae44c..c974cbf 100644 --- a/y-octo-node/Cargo.toml +++ b/y-octo-node/Cargo.toml @@ -16,7 +16,7 @@ napi = { version = "2", features = ["anyhow", "napi4", "serde-json"] } napi-derive = "2" serde_json = "1.0" y-octo = { workspace = true, features = [ - "large_refs", + "large_refs", ], default-features = false } [build-dependencies] diff --git a/y-octo-node/src/array.rs b/y-octo-node/src/array.rs index a24e794..d7c4c87 100644 --- a/y-octo-node/src/array.rs +++ b/y-octo-node/src/array.rs @@ -68,7 +68,8 @@ impl YArray { } #[napi( - ts_args_type = "value: YArray | YMap | YText | boolean | number | string | Record | null | undefined", + ts_args_type = "value: YArray | YMap | YText | boolean | number | string | Record | null | \ + undefined", ts_generic_types = "T = unknown", ts_return_type = "Array" )] diff --git a/y-octo-node/src/awareness.rs b/y-octo-node/src/awareness.rs index af4108c..bd5b195 100644 --- a/y-octo-node/src/awareness.rs +++ b/y-octo-node/src/awareness.rs @@ -14,9 +14,9 @@ impl YAwareness { pub fn new(client_id: Option) -> Self { let client_id = client_id .and_then(|c| c.try_into().ok()) - .unwrap_or_else(|| prefer_small_random()); + .unwrap_or_else(prefer_small_random); Self { - awareness: Awareness::new(client_id as u64), + awareness: Awareness::new(client_id), } } @@ -29,7 +29,7 @@ impl YAwareness { pub fn states(&self, env: Env) -> Result { let mut object = env.create_object()?; for (k, v) in self.awareness.get_states() { - let value = env.to_js_value(&serde_json::from_str(&v.content())?)?; + let value = env.to_js_value(&serde_json::from_str(v.content())?)?; object.set_named_property(&k.to_string(), value)?; } Ok(object) diff --git a/y-octo-node/src/function.rs b/y-octo-node/src/function.rs index dd62cd9..4c1c7af 100644 --- a/y-octo-node/src/function.rs +++ b/y-octo-node/src/function.rs @@ -1,4 +1,4 @@ -use napi::{bindgen_prelude::Buffer as JsBuffer, Env, JsUnknown}; +use napi::bindgen_prelude::Buffer as JsBuffer; use y_octo::{merge_updates_v1, CrdtWrite, RawEncoder}; use super::*; diff --git a/y-octo-node/src/lib.rs b/y-octo-node/src/lib.rs index cba8ac2..6e4608a 100644 --- a/y-octo-node/src/lib.rs +++ b/y-octo-node/src/lib.rs @@ -19,7 +19,4 @@ pub use map::*; pub use protocol::*; pub use text::*; pub use types::*; -use utils::{ - get_any_from_js_object, get_any_from_js_unknown, get_js_unknown_from_any, get_mixed_y_type_from_value, - MixedRefYType, MixedYType, -}; +use utils::{get_any_from_js_object, get_js_unknown_from_any, get_mixed_y_type_from_value, MixedRefYType, MixedYType}; diff --git a/y-octo-node/src/map.rs b/y-octo-node/src/map.rs index 51f89aa..3305ded 100644 --- a/y-octo-node/src/map.rs +++ b/y-octo-node/src/map.rs @@ -1,4 +1,4 @@ -use napi::{bindgen_prelude::Array as JsArray, iterator::Generator, Env, JsFunction, JsObject, JsUnknown, ValueType}; +use napi::{bindgen_prelude::Array as JsArray, iterator::Generator, Env, JsFunction, JsObject, ValueType}; use y_octo::{Any, Map, Value}; use super::*; @@ -106,7 +106,7 @@ impl YMap { ValueType::Object => match unknown.coerce_to_object().and_then(get_any_from_js_object) { Ok(any) => { self.map.insert(key, Value::Any(any.clone()))?; - Ok(MixedYType::D(get_js_unknown_from_any(env, any)?.into())) + Ok(MixedYType::D(get_js_unknown_from_any(env, any)?)) } Err(e) => Err(anyhow::Error::from(e).context("Failed to coerce value to object")), }, diff --git a/y-octo-node/src/protocol.rs b/y-octo-node/src/protocol.rs index 1db4cd1..c8229fb 100644 --- a/y-octo-node/src/protocol.rs +++ b/y-octo-node/src/protocol.rs @@ -1,9 +1,10 @@ -use super::*; use napi::bindgen_prelude::Buffer; use y_octo::{ read_doc_message, write_doc_message, CrdtRead, CrdtWrite, Doc, DocMessage, RawDecoder, RawEncoder, StateVector, }; +use super::*; + #[napi] pub struct YProtocol { pub(crate) doc: Doc, @@ -36,7 +37,7 @@ impl YProtocol { write_doc_message(&mut buffer, &DocMessage::Step2(update)).unwrap(); Ok(buffer.into()) } else { - Err(anyhow::Error::msg("State vector is required for sync step 2.").into()) + Err(anyhow::Error::msg("State vector is required for sync step 2.")) } } 3 => { @@ -49,7 +50,7 @@ impl YProtocol { write_doc_message(&mut buffer, &DocMessage::Update(update)).unwrap(); Ok(buffer.into()) } - _ => Err(anyhow::Error::msg("Invalid sync step. Must be 1, 2, or 3.").into()), + _ => Err(anyhow::Error::msg("Invalid sync step. Must be 1, 2, or 3.")), } } @@ -58,7 +59,7 @@ impl YProtocol { match read_doc_message(buffer.as_ref()) { Ok((tail, message)) => { if !tail.is_empty() { - return Err(anyhow::Error::msg("Invalid sync message buffer.").into()); + return Err(anyhow::Error::msg("Invalid sync message buffer.")); } Ok(match message { DocMessage::Step1(sv) => Some(self.encode_sync_step(2, Some(sv.into()))?), @@ -68,7 +69,7 @@ impl YProtocol { } }) } - Err(e) => Err(anyhow::Error::msg(format!("Invalid sync message buffer: {}", e.to_string())).into()), + Err(e) => Err(anyhow::Error::msg(format!("Invalid sync message buffer: {}", e))), } } } diff --git a/y-octo/src/doc/awareness.rs b/y-octo/src/doc/awareness.rs index 63cf32b..bd44b87 100644 --- a/y-octo/src/doc/awareness.rs +++ b/y-octo/src/doc/awareness.rs @@ -214,7 +214,7 @@ mod tests { let values: MutexGuard> = values.lock().unwrap(); assert_eq!(values.len(), 4); - let event = values.get(0).unwrap(); + let event = values.first().unwrap(); let mut added = event.added.clone(); added.sort(); diff --git a/y-octo/src/doc/codec/item_flag.rs b/y-octo/src/doc/codec/item_flag.rs index f019b7e..f90bf41 100644 --- a/y-octo/src/doc/codec/item_flag.rs +++ b/y-octo/src/doc/codec/item_flag.rs @@ -110,11 +110,11 @@ mod tests { fn test_flag_set_and_clear() { { let flag = super::ItemFlag::default(); - assert_eq!(flag.keep(), false); + assert!(!flag.keep()); flag.set_keep(); - assert_eq!(flag.keep(), true); + assert!(flag.keep()); flag.clear_keep(); - assert_eq!(flag.keep(), false); + assert!(!flag.keep()); assert_eq!( flag.0.load(Ordering::SeqCst), ItemFlag::default().0.load(Ordering::SeqCst) @@ -123,11 +123,11 @@ mod tests { { let flag = super::ItemFlag::default(); - assert_eq!(flag.countable(), false); + assert!(!flag.countable()); flag.set_countable(); - assert_eq!(flag.countable(), true); + assert!(flag.countable()); flag.clear_countable(); - assert_eq!(flag.countable(), false); + assert!(!flag.countable()); assert_eq!( flag.0.load(Ordering::SeqCst), ItemFlag::default().0.load(Ordering::SeqCst) @@ -136,11 +136,11 @@ mod tests { { let flag = super::ItemFlag::default(); - assert_eq!(flag.deleted(), false); + assert!(!flag.deleted()); flag.set_deleted(); - assert_eq!(flag.deleted(), true); + assert!(flag.deleted()); flag.clear_deleted(); - assert_eq!(flag.deleted(), false); + assert!(!flag.deleted()); assert_eq!( flag.0.load(Ordering::SeqCst), ItemFlag::default().0.load(Ordering::SeqCst) @@ -152,15 +152,15 @@ mod tests { flag.set_keep(); flag.set_countable(); flag.set_deleted(); - assert_eq!(flag.keep(), true); - assert_eq!(flag.countable(), true); - assert_eq!(flag.deleted(), true); + assert!(flag.keep()); + assert!(flag.countable()); + assert!(flag.deleted()); flag.clear_keep(); flag.clear_countable(); flag.clear_deleted(); - assert_eq!(flag.keep(), false); - assert_eq!(flag.countable(), false); - assert_eq!(flag.deleted(), false); + assert!(!flag.keep()); + assert!(!flag.countable()); + assert!(!flag.deleted()); assert_eq!( flag.0.load(Ordering::SeqCst), ItemFlag::default().0.load(Ordering::SeqCst) diff --git a/y-octo/src/doc/publisher.rs b/y-octo/src/doc/publisher.rs index df7e111..b3755eb 100644 --- a/y-octo/src/doc/publisher.rs +++ b/y-octo/src/doc/publisher.rs @@ -178,7 +178,7 @@ mod tests { loom_model!({ let doc = Doc::default(); - let ret = vec![ + let ret = [ vec![vec!["(1, 0)", "test.key1", "val1"]], vec![vec!["(1, 1)", "test.key2", "val2"], vec!["(1, 2)", "test.key3", "val3"]], vec![ diff --git a/y-octo/src/doc/types/text.rs b/y-octo/src/doc/types/text.rs index e701b65..977cd75 100644 --- a/y-octo/src/doc/types/text.rs +++ b/y-octo/src/doc/types/text.rs @@ -255,7 +255,7 @@ mod tests { loom_model!({ // in loom loop #[allow(clippy::needless_borrow)] - let doc = Doc::try_from_binary_v1(&binary).unwrap(); + let doc = Doc::try_from_binary_v1(binary).unwrap(); let mut text = doc.get_or_create_text("greating").unwrap(); assert_eq!(text.to_string(), "hello world"); From c78da2a0524ee348568f6cec1e4b253d33a8a7a8 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Tue, 10 Sep 2024 10:16:09 +0800 Subject: [PATCH 64/75] chore: copy new tests from yjs --- y-octo-node/tests/yjs/doc.spec.ts | 310 ++++++++++++++++++++++++++ y-octo-node/tests/yjs/y-array.spec.ts | 50 ++--- 2 files changed, 334 insertions(+), 26 deletions(-) create mode 100644 y-octo-node/tests/yjs/doc.spec.ts diff --git a/y-octo-node/tests/yjs/doc.spec.ts b/y-octo-node/tests/yjs/doc.spec.ts new file mode 100644 index 0000000..87be637 --- /dev/null +++ b/y-octo-node/tests/yjs/doc.spec.ts @@ -0,0 +1,310 @@ +import test from "ava"; +import * as Y from "../../index"; + +test.skip("testAfterTransactionRecursion", (t) => { + 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"); +}); + +test.skip("testOriginInTransaction", (t) => { + const doc = new Y.Doc(); + const ytext = doc.getText(); + /** + * @type {Array} + */ + const origins = []; + doc.on("afterTransaction", (tr) => { + origins.push(tr.origin); + if (origins.length <= 1) { + ytext.toDelta(Y.snapshot(doc)); // adding a snapshot forces toDelta to create a cleanup transaction + doc.transact(() => { + ytext.insert(0, "a"); + }, "nested"); + } + }); + doc.transact(() => { + ytext.insert(0, "0"); + }, "first"); + t.deepEqual(origins, ["first", "cleanup", "nested"]); +}); + +/** + * Client id should be changed when an instance receives updates from another client using the same client id. + */ +test.skip("testClientIdDuplicateChange", (t) => { + const doc1 = new Y.Doc(0); + const doc2 = new Y.Doc(0); + t.assert(doc2.clientID === doc1.clientID); + doc1.getArray("a").insert(0, [1, 2]); + Y.applyUpdate(doc2, Y.encodeStateAsUpdate(doc1)); + t.assert(doc2.clientID !== doc1.clientID); +}); + +test.skip("testGetTypeEmptyId", (t) => { + const doc1 = new Y.Doc(); + doc1.getText("").insert(0, "h"); + doc1.getText().insert(1, "i"); + const doc2 = new Y.Doc(); + Y.applyUpdate(doc2, Y.encodeStateAsUpdate(doc1)); + t.assert(doc2.getText().toString() === "hi"); + t.assert(doc2.getText("").toString() === "hi"); +}); + +test.skip("testToJSON", (t) => { + const doc = new Y.Doc(); + t.deepEqual(doc.toJSON(), {}, "doc.toJSON yields empty object"); + + const arr = doc.getArray("array"); + arr.push(["test1"]); + + const map = doc.getMap("map"); + map.set("k1", "v1"); + const map2 = new Y.Map(); + map.set("k2", map2); + map2.set("m2k1", "m2v1"); + + t.deepEqual( + doc.toJSON(), + { + array: ["test1"], + map: { + k1: "v1", + k2: { + m2k1: "m2v1", + }, + }, + }, + "doc.toJSON has array and recursive map", + ); +}); + +test.skip("testSubdoc", (t) => { + const doc = new Y.Doc(); + doc.load(); // doesn't do anything + { + /** + * @type {Array|null} + */ + let event = /** @type {any} */ null; + doc.on("subdocs", (subdocs) => { + event = [ + Array.from(subdocs.added).map((x) => x.guid), + Array.from(subdocs.removed).map((x) => x.guid), + Array.from(subdocs.loaded).map((x) => x.guid), + ]; + }); + const subdocs = doc.getMap("mysubdocs"); + const docA = new Y.Doc({ guid: "a" }); + docA.load(); + subdocs.set("a", docA); + t.deepEqual(event, [["a"], [], ["a"]]); + + event = null; + subdocs.get("a").load(); + t.assert(event === null); + + event = null; + subdocs.get("a").destroy(); + t.deepEqual(event, [["a"], ["a"], []]); + subdocs.get("a").load(); + t.deepEqual(event, [[], [], ["a"]]); + + subdocs.set("b", new Y.Doc({ guid: "a", shouldLoad: false })); + t.deepEqual(event, [["a"], [], []]); + subdocs.get("b").load(); + t.deepEqual(event, [[], [], ["a"]]); + + const docC = new Y.Doc({ guid: "c" }); + docC.load(); + subdocs.set("c", docC); + t.deepEqual(event, [["c"], [], ["c"]]); + + t.deepEqual(Array.from(doc.getSubdocGuids()), ["a", "c"]); + } + + const doc2 = new Y.Doc(); + { + t.deepEqual(Array.from(doc2.getSubdocs()), []); + /** + * @type {Array|null} + */ + let event = /** @type {any} */ null; + doc2.on("subdocs", (subdocs) => { + event = [ + Array.from(subdocs.added).map((d) => d.guid), + Array.from(subdocs.removed).map((d) => d.guid), + Array.from(subdocs.loaded).map((d) => d.guid), + ]; + }); + Y.applyUpdate(doc2, Y.encodeStateAsUpdate(doc)); + t.deepEqual(event, [["a", "a", "c"], [], []]); + + doc2.getMap("mysubdocs").get("a").load(); + t.deepEqual(event, [[], [], ["a"]]); + + t.deepEqual(Array.from(doc2.getSubdocGuids()), ["a", "c"]); + + doc2.getMap("mysubdocs").delete("a"); + t.deepEqual(event, [[], ["a"], []]); + t.deepEqual(Array.from(doc2.getSubdocGuids()), ["a", "c"]); + } +}); + +test.skip("testSubdocLoadEdgeCases", (t) => { + const ydoc = new Y.Doc(); + const yarray = ydoc.getArray(); + const subdoc1 = new Y.Doc(); + /** + * @type {any} + */ + let lastEvent = null; + ydoc.on("subdocs", (event) => { + lastEvent = event; + }); + yarray.insert(0, [subdoc1]); + t.assert(subdoc1.shouldLoad); + t.assert(subdoc1.autoLoad === false); + t.assert(lastEvent !== null && lastEvent.loaded.has(subdoc1)); + t.assert(lastEvent !== null && lastEvent.added.has(subdoc1)); + // destroy and check whether lastEvent adds it again to added (it shouldn't) + subdoc1.destroy(); + const subdoc2 = yarray.get(0); + t.assert(subdoc1 !== subdoc2); + t.assert(lastEvent !== null && lastEvent.added.has(subdoc2)); + t.assert(lastEvent !== null && !lastEvent.loaded.has(subdoc2)); + // load + subdoc2.load(); + t.assert(lastEvent !== null && !lastEvent.added.has(subdoc2)); + t.assert(lastEvent !== null && lastEvent.loaded.has(subdoc2)); + // apply from remote + const ydoc2 = new Y.Doc(); + ydoc2.on("subdocs", (event) => { + lastEvent = event; + }); + Y.applyUpdate(ydoc2, Y.encodeStateAsUpdate(ydoc)); + const subdoc3 = ydoc2.getArray().get(0); + t.assert(subdoc3.shouldLoad === false); + t.assert(subdoc3.autoLoad === false); + t.assert(lastEvent !== null && lastEvent.added.has(subdoc3)); + t.assert(lastEvent !== null && !lastEvent.loaded.has(subdoc3)); + // load + subdoc3.load(); + t.assert(subdoc3.shouldLoad); + t.assert(lastEvent !== null && !lastEvent.added.has(subdoc3)); + t.assert(lastEvent !== null && lastEvent.loaded.has(subdoc3)); +}); + +test.skip("testSubdocLoadEdgeCasesAutoload", (t) => { + const ydoc = new Y.Doc(); + const yarray = ydoc.getArray(); + const subdoc1 = new Y.Doc({ autoLoad: true }); + /** + * @type {any} + */ + let lastEvent = null; + ydoc.on("subdocs", (event) => { + lastEvent = event; + }); + yarray.insert(0, [subdoc1]); + t.assert(subdoc1.shouldLoad); + t.assert(subdoc1.autoLoad); + t.assert(lastEvent !== null && lastEvent.loaded.has(subdoc1)); + t.assert(lastEvent !== null && lastEvent.added.has(subdoc1)); + // destroy and check whether lastEvent adds it again to added (it shouldn't) + subdoc1.destroy(); + const subdoc2 = yarray.get(0); + t.assert(subdoc1 !== subdoc2); + t.assert(lastEvent !== null && lastEvent.added.has(subdoc2)); + t.assert(lastEvent !== null && !lastEvent.loaded.has(subdoc2)); + // load + subdoc2.load(); + t.assert(lastEvent !== null && !lastEvent.added.has(subdoc2)); + t.assert(lastEvent !== null && lastEvent.loaded.has(subdoc2)); + // apply from remote + const ydoc2 = new Y.Doc(); + ydoc2.on("subdocs", (event) => { + lastEvent = event; + }); + Y.applyUpdate(ydoc2, Y.encodeStateAsUpdate(ydoc)); + const subdoc3 = ydoc2.getArray().get(0); + t.assert(subdoc1.shouldLoad); + t.assert(subdoc1.autoLoad); + t.assert(lastEvent !== null && lastEvent.added.has(subdoc3)); + t.assert(lastEvent !== null && lastEvent.loaded.has(subdoc3)); +}); + +test.skip("testSubdocsUndo", (t) => { + const ydoc = new Y.Doc(); + const elems = ydoc.getXmlFragment(); + const undoManager = new Y.UndoManager(elems); + const subdoc = new Y.Doc(); + // @ts-ignore + elems.insert(0, [subdoc]); + undoManager.undo(); + undoManager.redo(); + t.assert(elems.length === 1); +}); + +test.skip("testLoadDocsEvent", async (t) => { + const ydoc = new Y.Doc(); + t.assert(ydoc.isLoaded === false); + let loadedEvent = false; + ydoc.on("load", () => { + loadedEvent = true; + }); + ydoc.emit("load", [ydoc]); + await ydoc.whenLoaded; + t.assert(loadedEvent); + t.assert(ydoc.isLoaded); +}); + +test.skip("testSyncDocsEvent", async (t) => { + 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/y-octo-node/tests/yjs/y-array.spec.ts b/y-octo-node/tests/yjs/y-array.spec.ts index eaf5b2a..b2d6d46 100644 --- a/y-octo-node/tests/yjs/y-array.spec.ts +++ b/y-octo-node/tests/yjs/y-array.spec.ts @@ -1,12 +1,10 @@ -import test from "ava"; +import { randomInt } from "node:crypto"; +import test, { ExecutionContext } from "ava"; +import * as prng from "lib0/prng"; +import * as math from "lib0/math"; import { init, compare, applyRandomTests } from "./testHelper"; - import * as Y from "../../index"; -import * as prng from "lib0/prng"; -import * as math from "lib0/math"; -import { randomInt } from "node:crypto"; -import assert, { deepEqual } from "node:assert"; const production = false; @@ -224,13 +222,13 @@ test.skip("testInsertAndDeleteEvents", (t) => { event = e; }); array0.insert(0, [0, 1, 2]); - assert(event !== null); + t.assert(event !== null); event = null; array0.delete(0); - assert(event !== null); + t.assert(event !== null); event = null; array0.delete(0, 2); - assert(event !== null); + t.assert(event !== null); event = null; compare(t, users); }); @@ -264,10 +262,10 @@ test.skip("testInsertAndDeleteEventsForTypes", (t) => { event = e; }); array0.insert(0, [new Y.Array()]); - assert(event !== null); + t.assert(event !== null); event = null; array0.delete(0); - assert(event !== null); + t.assert(event !== null); event = null; compare(t, users); }); @@ -292,7 +290,7 @@ test.skip("testObserveDeepEventOrder", (t) => { array0.insert(0, [0]); }); for (let i = 1; i < events.length; i++) { - assert( + t.assert( events[i - 1].path.length <= events[i].path.length, "path size increases, fire top-level events first", ); @@ -328,19 +326,19 @@ test.skip("testChangeEvent", (t) => { }); const newArr = new Y.Array(); array0.insert(0, [newArr, 4, "dtrn"]); - assert( + t.assert( changes !== null && changes.added.size === 2 && changes.deleted.size === 0, ); t.deepEqual(changes.delta, [{ insert: [newArr, 4, "dtrn"] }]); changes = null; array0.delete(0, 2); - assert( + t.assert( changes !== null && changes.added.size === 0 && changes.deleted.size === 2, ); t.deepEqual(changes.delta, [{ delete: 2 }]); changes = null; array0.insert(1, [0.1]); - assert( + t.assert( changes !== null && changes.added.size === 1 && changes.deleted.size === 0, ); t.deepEqual(changes.delta, [{ retain: 1 }, { insert: [0.1] }]); @@ -380,7 +378,7 @@ test.skip("testNewChildDoesNotEmitEventInTransaction", (t) => { array0.insert(0, [newMap]); newMap.set("tst", 42); }); - assert(!fired, "Event does not trigger"); + t.assert(!fired, "Event does not trigger"); }); test.skip("testGarbageCollector", (t) => { @@ -403,7 +401,7 @@ test.skip("testEventTargetIsSetCorrectlyOnLocal", (t) => { event = e; }); array0.insert(0, ["stuff"]); - assert(event.target === array0, '"target" property is set correctly'); + t.assert(event.target === array0, '"target" property is set correctly'); compare(t, users); }); @@ -416,7 +414,7 @@ test.skip("testEventTargetIsSetCorrectlyOnRemote", (t) => { }); array1.insert(0, ["stuff"]); testConnector.flushAllMessages(); - assert(event.target === array0, '"target" property is set correctly'); + t.assert(event.target === array0, '"target" property is set correctly'); compare(t, users); }); @@ -441,9 +439,9 @@ let _uniqueNumber = 0; const getUniqueNumber = () => _uniqueNumber++; const arrayTransactions: Array< - (arg0: Y.Doc, arg1: prng.PRNG, arg2: any) => void + (t: ExecutionContext, arg0: Y.Doc, arg1: prng.PRNG, arg2: any) => void > = [ - function insert(user: Y.Doc, gen: prng.PRNG) { + function insert(t, user, gen) { const yarray = user.getOrCreateArray("array"); const uniqueNumber = getUniqueNumber(); const content: number[] = []; @@ -455,16 +453,16 @@ const arrayTransactions: Array< const oldContent = yarray.toArray(); yarray.insert(pos, content); oldContent.splice(pos, 0, ...content); - deepEqual(yarray.toArray(), oldContent); // we want to make sure that fastSearch markers insert at the correct position + t.deepEqual(yarray.toArray(), oldContent); // we want to make sure that fastSearch markers insert at the correct position }, - function insertTypeArray(user: Y.Doc, gen: prng.PRNG) { + function insertTypeArray(t, user, gen) { const yarray = user.getOrCreateArray("array"); const pos = prng.int32(gen, 0, yarray.length); yarray.insert(pos, [user.createArray()]); const array2 = yarray.get(pos); array2.insert(0, [1, 2, 3, 4]); }, - function insertTypeMap(user: Y.Doc, gen: prng.PRNG) { + function insertTypeMap(t, user, gen) { const yarray = user.getOrCreateArray("array"); const pos = prng.int32(gen, 0, yarray.length); yarray.insert(pos, [user.createMap()]); @@ -473,12 +471,12 @@ const arrayTransactions: Array< map.set("someprop", 43); map.set("someprop", 44); }, - function insertTypeNull(user: Y.Doc, gen: prng.PRNG) { + function insertTypeNull(t, user, gen) { const yarray = user.getOrCreateArray("array"); const pos = prng.int32(gen, 0, yarray.length); yarray.insert(pos, [null]); }, - function _delete(user: Y.Doc, gen: prng.PRNG) { + function _delete(t, user, gen) { const yarray = user.getOrCreateArray("array"); const length = yarray.length; if (length > 0) { @@ -495,7 +493,7 @@ const arrayTransactions: Array< const oldContent = yarray.toArray(); yarray.delete(somePos, delLength); oldContent.splice(somePos, delLength); - deepEqual(yarray.toArray(), oldContent); + t.deepEqual(yarray.toArray(), oldContent); } } }, From 391bd7be09b766e8e185d1ab632981ae11b5b6ce Mon Sep 17 00:00:00 2001 From: DarkSky Date: Tue, 10 Sep 2024 15:02:16 +0800 Subject: [PATCH 65/75] feat: improve source bundle --- package.json | 9 +- y-octo-node/.gitignore | 1 + y-octo-node/package.json | 14 +- y-octo-node/{ => src}/index.ts | 2 +- y-octo-node/{ => src}/yjs.d.ts | 0 y-octo-node/{ => src}/yocto.d.ts | 118 +- y-octo-node/src/yocto.js | 393 +++++ y-octo-node/tests/array.spec.ts | 2 +- y-octo-node/tests/doc.spec.ts | 2 +- y-octo-node/tests/map.spec.ts | 2 +- y-octo-node/tests/text.spec.ts | 2 +- y-octo-node/tests/yjs/doc.spec.ts | 2 +- y-octo-node/tests/yjs/testHelper.ts | 2 +- y-octo-node/tests/yjs/y-array.spec.ts | 2 +- y-octo-node/tests/yjs/y-map.spec.ts | 2 +- y-octo-node/tests/yjs/y-text.spec.ts | 2 +- y-octo-node/tsconfig.json | 4 +- y-octo-node/yocto.js | 339 ----- yarn.lock | 1900 ++++++++++++++++++++++++- 19 files changed, 2338 insertions(+), 460 deletions(-) rename y-octo-node/{ => src}/index.ts (99%) rename y-octo-node/{ => src}/yjs.d.ts (100%) rename y-octo-node/{ => src}/yocto.d.ts (76%) create mode 100644 y-octo-node/src/yocto.js delete mode 100644 y-octo-node/yocto.js diff --git a/package.json b/package.json index 745e055..5d64e5d 100644 --- a/package.json +++ b/package.json @@ -51,24 +51,28 @@ "get-symbol-description": "npm:@nolyfill/get-symbol-description@^1", "globalthis": "npm:@nolyfill/globalthis@^1", "gopd": "npm:@nolyfill/gopd@^1", - "has": "npm:@nolyfill/has@^1", "has-property-descriptors": "npm:@nolyfill/has-property-descriptors@^1", "has-proto": "npm:@nolyfill/has-proto@^1", "has-symbols": "npm:@nolyfill/has-symbols@^1", "has-tostringtag": "npm:@nolyfill/has-tostringtag@^1", + "hasown": "npm:@nolyfill/hasown@^1", "internal-slot": "npm:@nolyfill/internal-slot@^1", "is-array-buffer": "npm:@nolyfill/is-array-buffer@^1", + "is-core-module": "npm:@nolyfill/is-core-module@^1", "is-date-object": "npm:@nolyfill/is-date-object@^1", "is-regex": "npm:@nolyfill/is-regex@^1", "is-shared-array-buffer": "npm:@nolyfill/is-shared-array-buffer@^1", "is-string": "npm:@nolyfill/is-string@^1", "is-symbol": "npm:@nolyfill/is-symbol@^1", + "is-typed-array": "npm:@nolyfill/is-typed-array@^1", "is-weakref": "npm:@nolyfill/is-weakref@^1", + "isarray": "npm:@nolyfill/isarray@^1", "object-keys": "npm:@nolyfill/object-keys@^1", "object.assign": "npm:@nolyfill/object.assign@^1", "regexp.prototype.flags": "npm:@nolyfill/regexp.prototype.flags@^1", "safe-array-concat": "npm:@nolyfill/safe-array-concat@^1", "safe-regex-test": "npm:@nolyfill/safe-regex-test@^1", + "set-function-length": "npm:@nolyfill/set-function-length@^1", "side-channel": "npm:@nolyfill/side-channel@^1", "string.prototype.padend": "npm:@nolyfill/string.prototype.padend@^1", "string.prototype.trim": "npm:@nolyfill/string.prototype.trim@^1", @@ -80,7 +84,6 @@ "typed-array-length": "npm:@nolyfill/typed-array-length@^1", "unbox-primitive": "npm:@nolyfill/unbox-primitive@^1", "which-boxed-primitive": "npm:@nolyfill/which-boxed-primitive@^1", - "which-typed-array": "npm:@nolyfill/which-typed-array@^1", - "is-core-module": "npm:@nolyfill/is-core-module@^1" + "which-typed-array": "npm:@nolyfill/which-typed-array@^1" } } diff --git a/y-octo-node/.gitignore b/y-octo-node/.gitignore index 28f2c68..c1d1555 100644 --- a/y-octo-node/.gitignore +++ b/y-octo-node/.gitignore @@ -1,2 +1,3 @@ +/lib *.node .coverage \ No newline at end of file diff --git a/y-octo-node/package.json b/y-octo-node/package.json index 6e2951f..77e8614 100644 --- a/y-octo-node/package.json +++ b/y-octo-node/package.json @@ -1,8 +1,10 @@ { "name": "@y-octo/node", "private": true, - "main": "yocto.js", - "types": "yocto.d.ts", + "main": "./dist/index.js", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "exports": "./dist/index.js", "napi": { "name": "y-octo", "triples": { @@ -20,11 +22,12 @@ }, "license": "MIT", "devDependencies": { - "@napi-rs/cli": "^2.18.4", + "@napi-rs/cli": "^3.0.0-alpha.62", "@types/node": "^20.16.4", "@types/prompts": "^2.4.4", "ava": "^6.1.3", "c8": "^10.1.2", + "pkgroll": "^2.5.0", "prompts": "^2.4.2", "tsx": "^4.16.2", "typescript": "^5.1.6", @@ -35,8 +38,9 @@ }, "scripts": { "artifacts": "napi artifacts", - "build": "napi build --platform --release --no-const-enum --dts yocto.d.ts --js yocto.js", - "build:debug": "napi build --platform --no-const-enum --dts yocto.d.ts --js yocto.js", + "build": "napi build --platform --release --no-const-enum --dts ../src/yocto.d.ts --js ../src/yocto.js -o ./dist", + "build:js": "pkgroll", + "build:debug": "napi build --platform --no-const-enum --dts ../src/yocto.d.ts --js ../src/yocto.js -o ./dist", "universal": "napi universal", "test": "ava --concurrency 1 --serial --timeout 5s", "test:watch": "yarn exec tsx ./scripts/run-test.mts all --watch", diff --git a/y-octo-node/index.ts b/y-octo-node/src/index.ts similarity index 99% rename from y-octo-node/index.ts rename to y-octo-node/src/index.ts index 76bb2a0..cc4fb1d 100644 --- a/y-octo-node/index.ts +++ b/y-octo-node/src/index.ts @@ -68,7 +68,7 @@ export class Doc extends Y.Doc { if (this.lastState?.length && diff?.length) { this.subscribers.forEach((callback) => - callback(new Uint8Array(diff), origin || this), + callback(new Uint8Array(diff!), origin || this), ); } } diff --git a/y-octo-node/yjs.d.ts b/y-octo-node/src/yjs.d.ts similarity index 100% rename from y-octo-node/yjs.d.ts rename to y-octo-node/src/yjs.d.ts diff --git a/y-octo-node/yocto.d.ts b/y-octo-node/src/yocto.d.ts similarity index 76% rename from y-octo-node/yocto.d.ts rename to y-octo-node/src/yocto.d.ts index 6ce8d9a..d686149 100644 --- a/y-octo-node/yocto.d.ts +++ b/y-octo-node/src/yocto.d.ts @@ -1,46 +1,17 @@ -/* tslint:disable */ -/* eslint-disable */ - /* auto-generated by NAPI-RS */ - -export declare function encodeStateAsUpdate(doc: Doc, state?: Buffer | undefined | null): Buffer -export declare function encodeStateVector(doc: Doc): Buffer -export declare function compareStructStores(store: YStore, other: YStore): boolean -export declare function compareIds(a?: YId | undefined | null, b?: YId | undefined | null): boolean -export declare function createDeleteSetFromStructStore(store: YStore): YDeleteSet -export declare function equalDeleteSets(a: YDeleteSet, b: YDeleteSet): boolean -export declare function snapshot(doc: Doc): YSnapshot -export declare function encodeSnapshot(snapshot: YSnapshot): Buffer -export declare function applyUpdate(doc: Doc, update: Buffer): void -export declare function mergeUpdates(updates: Array): Buffer -export declare function isAbstractType(obj?: any): boolean -export declare class YArray { - get length(): number - get isEmpty(): boolean - get itemId(): YId | null - get(index: number): T - slice(start: number, end?: number | undefined | null): Array - map(value: YArray | YMap | YText | boolean | number | string | Record | null | undefined): Array - insert(index: number, value: YArray | YMap | YText | boolean | number | string | Record | null | undefined): void - push(value: YArray | YMap | YText | boolean | number | string | Record | null | undefined): void - unshift(value: YArray | YMap | YText | boolean | number | string | Record | null | undefined): void - delete(index: number, len?: number | undefined | null): void - iter(): YArrayIterator - toArray(): Array - toJSON(): Array - observe(callback: (...args: any[]) => any): void - observeDeep(callback: (...args: any[]) => any): void -} -export declare class YArrayIterator { - [Symbol.iterator](): Iterator -} -export type YAwareness = Awareness +/* eslint-disable */ export declare class Awareness { constructor(clientId?: number | undefined | null) get clientId(): number get states(): Record } -export type YDoc = Doc +export type YAwareness = Awareness + +export declare class DeleteSet { + +} +export type YDeleteSet = DeleteSet + export declare class Doc { constructor(clientId?: number | undefined | null) get clientId(): number @@ -62,6 +33,40 @@ export declare class Doc { offUpdate(): void destroy(): void } +export type YDoc = Doc + +export declare class Id { + +} +export type YId = Id + +export declare class Store { + +} +export type YStore = Store + +export declare class YArray { + get length(): number + get isEmpty(): boolean + get itemId(): YId | null + get(index: number): T + slice(start: number, end?: number | undefined | null): Array + map(value: YArray | YMap | YText | boolean | number | string | Record | null | undefined): Array + insert(index: number, value: YArray | YMap | YText | boolean | number | string | Record | null | undefined): void + push(value: YArray | YMap | YText | boolean | number | string | Record | null | undefined): void + unshift(value: YArray | YMap | YText | boolean | number | string | Record | null | undefined): void + delete(index: number, len?: number | undefined | null): void + iter(): YArrayIterator + toArray(): Array + toJSON(): Array + observe(callback: (...args: any[]) => any): void + observeDeep(callback: (...args: any[]) => any): void +} + +export declare class YArrayIterator { + [Symbol.iterator](): Iterator +} + export declare class YMap { get length(): number get size(): number @@ -78,20 +83,29 @@ export declare class YMap { observe(callback: (...args: any[]) => any): void observeDeep(callback: (...args: any[]) => any): void } + export declare class YMapEntriesIterator { [Symbol.iterator](): Iterator } + export declare class YMapKeyIterator { [Symbol.iterator](): Iterator } + export declare class YMapValuesIterator { [Symbol.iterator](): Iterator } + export declare class YProtocol { constructor(doc: Doc) encodeSyncStep(step: number, buffer?: Buffer | undefined | null): Buffer applySyncStep(buffer: Buffer): Buffer | null } + +export declare class YSnapshot { + +} + export declare class YText { get len(): number get isEmpty(): boolean @@ -104,10 +118,26 @@ export declare class YText { observe(callback: (...args: any[]) => any): void observeDeep(callback: (...args: any[]) => any): void } -export type YId = Id -export declare class Id { } -export type YStore = Store -export declare class Store { } -export type YDeleteSet = DeleteSet -export declare class DeleteSet { } -export declare class YSnapshot { } + +export declare export declare function applyUpdate(doc: Doc, update: Buffer): void + +export declare export declare function compareIds(a?: YId | undefined | null, b?: YId | undefined | null): boolean + +export declare export declare function compareStructStores(store: YStore, other: YStore): boolean + +export declare export declare function createDeleteSetFromStructStore(store: YStore): YDeleteSet + +export declare export declare function encodeSnapshot(snapshot: YSnapshot): Buffer + +export declare export declare function encodeStateAsUpdate(doc: Doc, state?: Buffer | undefined | null): Buffer + +export declare export declare function encodeStateVector(doc: Doc): Buffer + +export declare export declare function equalDeleteSets(a: YDeleteSet, b: YDeleteSet): boolean + +export declare export declare function isAbstractType(obj?: any): boolean + +export declare export declare function mergeUpdates(updates: Array): Buffer + +export declare export declare function snapshot(doc: Doc): YSnapshot + diff --git a/y-octo-node/src/yocto.js b/y-octo-node/src/yocto.js new file mode 100644 index 0000000..c912ad2 --- /dev/null +++ b/y-octo-node/src/yocto.js @@ -0,0 +1,393 @@ +// prettier-ignore +/* eslint-disable */ +/* auto-generated by NAPI-RS */ + +const { readFileSync } = require('fs') + +let nativeBinding = null +const loadErrors = [] + +const isMusl = () => { + let musl = false + if (process.platform === 'linux') { + musl = isMuslFromFilesystem() + if (musl === null) { + musl = isMuslFromReport() + } + if (musl === null) { + musl = isMuslFromChildProcess() + } + } + return musl +} + +const isFileMusl = (f) => f.includes('libc.musl-') || f.includes('ld-musl-') + +const isMuslFromFilesystem = () => { + try { + return readFileSync('/usr/bin/ldd', 'utf-8').includes('musl') + } catch { + return null + } +} + +const isMuslFromReport = () => { + const report = typeof process.report.getReport === 'function' ? process.report.getReport() : null + if (!report) { + return null + } + if (report.header && report.header.glibcVersionRuntime) { + return false + } + if (Array.isArray(report.sharedObjects)) { + if (report.sharedObjects.some(isFileMusl)) { + return true + } + } + return false +} + +const isMuslFromChildProcess = () => { + try { + return require('child_process').execSync('ldd --version', { encoding: 'utf8' }).includes('musl') + } catch (e) { + // If we reach this case, we don't know if the system is musl or not, so is better to just fallback to false + return false + } +} + +function requireNative() { + if (process.platform === 'android') { + if (process.arch === 'arm64') { + try { + return require('./y-octo.android-arm64.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@y-octo/node-android-arm64') + } catch (e) { + loadErrors.push(e) + } + + } else if (process.arch === 'arm') { + try { + return require('./y-octo.android-arm-eabi.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@y-octo/node-android-arm-eabi') + } catch (e) { + loadErrors.push(e) + } + + } else { + loadErrors.push(new Error(`Unsupported architecture on Android ${process.arch}`)) + } + } else if (process.platform === 'win32') { + if (process.arch === 'x64') { + try { + return require('./y-octo.win32-x64-msvc.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@y-octo/node-win32-x64-msvc') + } catch (e) { + loadErrors.push(e) + } + + } else if (process.arch === 'ia32') { + try { + return require('./y-octo.win32-ia32-msvc.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@y-octo/node-win32-ia32-msvc') + } catch (e) { + loadErrors.push(e) + } + + } else if (process.arch === 'arm64') { + try { + return require('./y-octo.win32-arm64-msvc.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@y-octo/node-win32-arm64-msvc') + } catch (e) { + loadErrors.push(e) + } + + } else { + loadErrors.push(new Error(`Unsupported architecture on Windows: ${process.arch}`)) + } + } else if (process.platform === 'darwin') { + try { + return require('./y-octo.darwin-universal.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@y-octo/node-darwin-universal') + } catch (e) { + loadErrors.push(e) + } + + if (process.arch === 'x64') { + try { + return require('./y-octo.darwin-x64.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@y-octo/node-darwin-x64') + } catch (e) { + loadErrors.push(e) + } + + } else if (process.arch === 'arm64') { + try { + return require('./y-octo.darwin-arm64.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@y-octo/node-darwin-arm64') + } catch (e) { + loadErrors.push(e) + } + + } else { + loadErrors.push(new Error(`Unsupported architecture on macOS: ${process.arch}`)) + } + } else if (process.platform === 'freebsd') { + if (process.arch === 'x64') { + try { + return require('./y-octo.freebsd-x64.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@y-octo/node-freebsd-x64') + } catch (e) { + loadErrors.push(e) + } + + } else if (process.arch === 'arm64') { + try { + return require('./y-octo.freebsd-arm64.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@y-octo/node-freebsd-arm64') + } catch (e) { + loadErrors.push(e) + } + + } else { + loadErrors.push(new Error(`Unsupported architecture on FreeBSD: ${process.arch}`)) + } + } else if (process.platform === 'linux') { + if (process.arch === 'x64') { + if (isMusl()) { + try { + return require('./y-octo.linux-x64-musl.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@y-octo/node-linux-x64-musl') + } catch (e) { + loadErrors.push(e) + } + + } else { + try { + return require('./y-octo.linux-x64-gnu.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@y-octo/node-linux-x64-gnu') + } catch (e) { + loadErrors.push(e) + } + + } + } else if (process.arch === 'arm64') { + if (isMusl()) { + try { + return require('./y-octo.linux-arm64-musl.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@y-octo/node-linux-arm64-musl') + } catch (e) { + loadErrors.push(e) + } + + } else { + try { + return require('./y-octo.linux-arm64-gnu.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@y-octo/node-linux-arm64-gnu') + } catch (e) { + loadErrors.push(e) + } + + } + } else if (process.arch === 'arm') { + if (isMusl()) { + try { + return require('./y-octo.linux-arm-musleabihf.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@y-octo/node-linux-arm-musleabihf') + } catch (e) { + loadErrors.push(e) + } + + } else { + try { + return require('./y-octo.linux-arm-gnueabihf.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@y-octo/node-linux-arm-gnueabihf') + } catch (e) { + loadErrors.push(e) + } + + } + } else if (process.arch === 'riscv64') { + if (isMusl()) { + try { + return require('./y-octo.linux-riscv64-musl.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@y-octo/node-linux-riscv64-musl') + } catch (e) { + loadErrors.push(e) + } + + } else { + try { + return require('./y-octo.linux-riscv64-gnu.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@y-octo/node-linux-riscv64-gnu') + } catch (e) { + loadErrors.push(e) + } + + } + } else if (process.arch === 'ppc64') { + try { + return require('./y-octo.linux-ppc64-gnu.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@y-octo/node-linux-ppc64-gnu') + } catch (e) { + loadErrors.push(e) + } + + } else if (process.arch === 's390x') { + try { + return require('./y-octo.linux-s390x-gnu.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@y-octo/node-linux-s390x-gnu') + } catch (e) { + loadErrors.push(e) + } + + } else { + loadErrors.push(new Error(`Unsupported architecture on Linux: ${process.arch}`)) + } + } else { + loadErrors.push(new Error(`Unsupported OS: ${process.platform}, architecture: ${process.arch}`)) + } +} + +nativeBinding = requireNative() + +if (!nativeBinding || process.env.NAPI_RS_FORCE_WASI) { + try { + nativeBinding = require('./y-octo.wasi.cjs') + } catch (err) { + if (process.env.NAPI_RS_FORCE_WASI) { + loadErrors.push(err) + } + } + if (!nativeBinding) { + try { + nativeBinding = require('@y-octo/node-wasm32-wasi') + } catch (err) { + if (process.env.NAPI_RS_FORCE_WASI) { + loadErrors.push(err) + } + } + } +} + +if (!nativeBinding) { + if (loadErrors.length > 0) { + // TODO Link to documentation with potential fixes + // - The package owner could build/publish bindings for this arch + // - The user may need to bundle the correct files + // - The user may need to re-install node_modules to get new packages + throw new Error('Failed to load native binding', { cause: loadErrors }) + } + throw new Error(`Failed to load native binding`) +} + +module.exports.Awareness = nativeBinding.Awareness +module.exports.YAwareness = nativeBinding.YAwareness +module.exports.DeleteSet = nativeBinding.DeleteSet +module.exports.YDeleteSet = nativeBinding.YDeleteSet +module.exports.Doc = nativeBinding.Doc +module.exports.YDoc = nativeBinding.YDoc +module.exports.Id = nativeBinding.Id +module.exports.YId = nativeBinding.YId +module.exports.Store = nativeBinding.Store +module.exports.YStore = nativeBinding.YStore +module.exports.YArray = nativeBinding.YArray +module.exports.YArrayIterator = nativeBinding.YArrayIterator +module.exports.YMap = nativeBinding.YMap +module.exports.YMapEntriesIterator = nativeBinding.YMapEntriesIterator +module.exports.YMapKeyIterator = nativeBinding.YMapKeyIterator +module.exports.YMapValuesIterator = nativeBinding.YMapValuesIterator +module.exports.YProtocol = nativeBinding.YProtocol +module.exports.YSnapshot = nativeBinding.YSnapshot +module.exports.YText = nativeBinding.YText +module.exports.applyUpdate = nativeBinding.applyUpdate +module.exports.compareIds = nativeBinding.compareIds +module.exports.compareStructStores = nativeBinding.compareStructStores +module.exports.createDeleteSetFromStructStore = nativeBinding.createDeleteSetFromStructStore +module.exports.encodeSnapshot = nativeBinding.encodeSnapshot +module.exports.encodeStateAsUpdate = nativeBinding.encodeStateAsUpdate +module.exports.encodeStateVector = nativeBinding.encodeStateVector +module.exports.equalDeleteSets = nativeBinding.equalDeleteSets +module.exports.isAbstractType = nativeBinding.isAbstractType +module.exports.mergeUpdates = nativeBinding.mergeUpdates +module.exports.snapshot = nativeBinding.snapshot diff --git a/y-octo-node/tests/array.spec.ts b/y-octo-node/tests/array.spec.ts index 61918f7..79ab9ac 100644 --- a/y-octo-node/tests/array.spec.ts +++ b/y-octo-node/tests/array.spec.ts @@ -1,6 +1,6 @@ import test from "ava"; -import * as YOcto from "../index"; +import * as YOcto from "@y-octo/node"; let client_id: number; let doc: YOcto.Doc; diff --git a/y-octo-node/tests/doc.spec.ts b/y-octo-node/tests/doc.spec.ts index 2a46c2c..10dbe0d 100644 --- a/y-octo-node/tests/doc.spec.ts +++ b/y-octo-node/tests/doc.spec.ts @@ -1,6 +1,6 @@ import test from "ava"; -import * as YOcto from "../index"; +import * as YOcto from "@y-octo/node"; import * as Y from "yjs"; let client_id: number; diff --git a/y-octo-node/tests/map.spec.ts b/y-octo-node/tests/map.spec.ts index 534a813..2034cc6 100644 --- a/y-octo-node/tests/map.spec.ts +++ b/y-octo-node/tests/map.spec.ts @@ -1,7 +1,7 @@ import test from "ava"; import * as Y from "yjs"; -import * as YOcto from "../index"; +import * as YOcto from "@y-octo/node"; let client_id: number; let doc: YOcto.Doc; diff --git a/y-octo-node/tests/text.spec.ts b/y-octo-node/tests/text.spec.ts index dbe6b42..10bbfbc 100644 --- a/y-octo-node/tests/text.spec.ts +++ b/y-octo-node/tests/text.spec.ts @@ -1,6 +1,6 @@ import test from "ava"; -import * as YOcto from "../index"; +import * as YOcto from "@y-octo/node"; let client_id: number; let doc: YOcto.Doc; diff --git a/y-octo-node/tests/yjs/doc.spec.ts b/y-octo-node/tests/yjs/doc.spec.ts index 87be637..a656985 100644 --- a/y-octo-node/tests/yjs/doc.spec.ts +++ b/y-octo-node/tests/yjs/doc.spec.ts @@ -1,5 +1,5 @@ import test from "ava"; -import * as Y from "../../index"; +import * as Y from "@y-octo/node"; test.skip("testAfterTransactionRecursion", (t) => { const ydoc = new Y.Doc(); diff --git a/y-octo-node/tests/yjs/testHelper.ts b/y-octo-node/tests/yjs/testHelper.ts index 7e673c8..8000c2a 100644 --- a/y-octo-node/tests/yjs/testHelper.ts +++ b/y-octo-node/tests/yjs/testHelper.ts @@ -1,7 +1,7 @@ import * as prng from "lib0/prng"; import * as object from "lib0/object"; import * as map from "lib0/map"; -import * as Y from "../../index"; +import * as Y from "@y-octo/node"; import { ExecutionContext } from "ava"; if (typeof window !== "undefined") { diff --git a/y-octo-node/tests/yjs/y-array.spec.ts b/y-octo-node/tests/yjs/y-array.spec.ts index b2d6d46..4037a25 100644 --- a/y-octo-node/tests/yjs/y-array.spec.ts +++ b/y-octo-node/tests/yjs/y-array.spec.ts @@ -4,7 +4,7 @@ import * as prng from "lib0/prng"; import * as math from "lib0/math"; import { init, compare, applyRandomTests } from "./testHelper"; -import * as Y from "../../index"; +import * as Y from "@y-octo/node"; const production = false; diff --git a/y-octo-node/tests/yjs/y-map.spec.ts b/y-octo-node/tests/yjs/y-map.spec.ts index 7c6d336..71ce403 100644 --- a/y-octo-node/tests/yjs/y-map.spec.ts +++ b/y-octo-node/tests/yjs/y-map.spec.ts @@ -3,7 +3,7 @@ import test, { ExecutionContext } from "ava"; import { init, compare, applyRandomTests } from "./testHelper.js"; -import * as Y from "../../index"; +import * as Y from "@y-octo/node"; import * as prng from "lib0/prng"; const production = false; diff --git a/y-octo-node/tests/yjs/y-text.spec.ts b/y-octo-node/tests/yjs/y-text.spec.ts index 5e3562d..a1e13de 100644 --- a/y-octo-node/tests/yjs/y-text.spec.ts +++ b/y-octo-node/tests/yjs/y-text.spec.ts @@ -4,7 +4,7 @@ import test, { ExecutionContext } from "ava"; import * as prng from "lib0/prng"; import * as math from "lib0/math"; -import * as Y from "../../index"; +import * as Y from "@y-octo/node"; import { applyRandomTests, compare, init } from "./testHelper"; let gen: prng.PRNG; diff --git a/y-octo-node/tsconfig.json b/y-octo-node/tsconfig.json index edc64f5..9560070 100644 --- a/y-octo-node/tsconfig.json +++ b/y-octo-node/tsconfig.json @@ -2,10 +2,10 @@ "extends": "../tsconfig.json", "compilerOptions": { "noEmit": false, - "outDir": "lib", + "outDir": "dist", "composite": true }, - "include": ["index.d.ts", "tests/**/*.(mts|ts)"], + "include": ["src/**/*.(tsx?)", "tests/**/*.(mts|ts)"], "ts-node": { "esm": true, "experimentalSpecifierResolution": "node" diff --git a/y-octo-node/yocto.js b/y-octo-node/yocto.js deleted file mode 100644 index 2ee8d5e..0000000 --- a/y-octo-node/yocto.js +++ /dev/null @@ -1,339 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -/* prettier-ignore */ - -/* auto-generated by NAPI-RS */ - -const { existsSync, readFileSync } = require('fs') -const { join } = require('path') - -const { platform, arch } = process - -let nativeBinding = null -let localFileExisted = false -let loadError = null - -function isMusl() { - // For Node 10 - if (!process.report || typeof process.report.getReport !== 'function') { - try { - const lddPath = require('child_process').execSync('which ldd').toString().trim() - return readFileSync(lddPath, 'utf8').includes('musl') - } catch (e) { - return true - } - } else { - const { glibcVersionRuntime } = process.report.getReport().header - return !glibcVersionRuntime - } -} - -switch (platform) { - case 'android': - switch (arch) { - case 'arm64': - localFileExisted = existsSync(join(__dirname, 'y-octo.android-arm64.node')) - try { - if (localFileExisted) { - nativeBinding = require('./y-octo.android-arm64.node') - } else { - nativeBinding = require('@y-octo/node-android-arm64') - } - } catch (e) { - loadError = e - } - break - case 'arm': - localFileExisted = existsSync(join(__dirname, 'y-octo.android-arm-eabi.node')) - try { - if (localFileExisted) { - nativeBinding = require('./y-octo.android-arm-eabi.node') - } else { - nativeBinding = require('@y-octo/node-android-arm-eabi') - } - } catch (e) { - loadError = e - } - break - default: - throw new Error(`Unsupported architecture on Android ${arch}`) - } - break - case 'win32': - switch (arch) { - case 'x64': - localFileExisted = existsSync( - join(__dirname, 'y-octo.win32-x64-msvc.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./y-octo.win32-x64-msvc.node') - } else { - nativeBinding = require('@y-octo/node-win32-x64-msvc') - } - } catch (e) { - loadError = e - } - break - case 'ia32': - localFileExisted = existsSync( - join(__dirname, 'y-octo.win32-ia32-msvc.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./y-octo.win32-ia32-msvc.node') - } else { - nativeBinding = require('@y-octo/node-win32-ia32-msvc') - } - } catch (e) { - loadError = e - } - break - case 'arm64': - localFileExisted = existsSync( - join(__dirname, 'y-octo.win32-arm64-msvc.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./y-octo.win32-arm64-msvc.node') - } else { - nativeBinding = require('@y-octo/node-win32-arm64-msvc') - } - } catch (e) { - loadError = e - } - break - default: - throw new Error(`Unsupported architecture on Windows: ${arch}`) - } - break - case 'darwin': - localFileExisted = existsSync(join(__dirname, 'y-octo.darwin-universal.node')) - try { - if (localFileExisted) { - nativeBinding = require('./y-octo.darwin-universal.node') - } else { - nativeBinding = require('@y-octo/node-darwin-universal') - } - break - } catch {} - switch (arch) { - case 'x64': - localFileExisted = existsSync(join(__dirname, 'y-octo.darwin-x64.node')) - try { - if (localFileExisted) { - nativeBinding = require('./y-octo.darwin-x64.node') - } else { - nativeBinding = require('@y-octo/node-darwin-x64') - } - } catch (e) { - loadError = e - } - break - case 'arm64': - localFileExisted = existsSync( - join(__dirname, 'y-octo.darwin-arm64.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./y-octo.darwin-arm64.node') - } else { - nativeBinding = require('@y-octo/node-darwin-arm64') - } - } catch (e) { - loadError = e - } - break - default: - throw new Error(`Unsupported architecture on macOS: ${arch}`) - } - break - case 'freebsd': - if (arch !== 'x64') { - throw new Error(`Unsupported architecture on FreeBSD: ${arch}`) - } - localFileExisted = existsSync(join(__dirname, 'y-octo.freebsd-x64.node')) - try { - if (localFileExisted) { - nativeBinding = require('./y-octo.freebsd-x64.node') - } else { - nativeBinding = require('@y-octo/node-freebsd-x64') - } - } catch (e) { - loadError = e - } - break - case 'linux': - switch (arch) { - case 'x64': - if (isMusl()) { - localFileExisted = existsSync( - join(__dirname, 'y-octo.linux-x64-musl.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./y-octo.linux-x64-musl.node') - } else { - nativeBinding = require('@y-octo/node-linux-x64-musl') - } - } catch (e) { - loadError = e - } - } else { - localFileExisted = existsSync( - join(__dirname, 'y-octo.linux-x64-gnu.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./y-octo.linux-x64-gnu.node') - } else { - nativeBinding = require('@y-octo/node-linux-x64-gnu') - } - } catch (e) { - loadError = e - } - } - break - case 'arm64': - if (isMusl()) { - localFileExisted = existsSync( - join(__dirname, 'y-octo.linux-arm64-musl.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./y-octo.linux-arm64-musl.node') - } else { - nativeBinding = require('@y-octo/node-linux-arm64-musl') - } - } catch (e) { - loadError = e - } - } else { - localFileExisted = existsSync( - join(__dirname, 'y-octo.linux-arm64-gnu.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./y-octo.linux-arm64-gnu.node') - } else { - nativeBinding = require('@y-octo/node-linux-arm64-gnu') - } - } catch (e) { - loadError = e - } - } - break - case 'arm': - if (isMusl()) { - localFileExisted = existsSync( - join(__dirname, 'y-octo.linux-arm-musleabihf.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./y-octo.linux-arm-musleabihf.node') - } else { - nativeBinding = require('@y-octo/node-linux-arm-musleabihf') - } - } catch (e) { - loadError = e - } - } else { - localFileExisted = existsSync( - join(__dirname, 'y-octo.linux-arm-gnueabihf.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./y-octo.linux-arm-gnueabihf.node') - } else { - nativeBinding = require('@y-octo/node-linux-arm-gnueabihf') - } - } catch (e) { - loadError = e - } - } - break - case 'riscv64': - if (isMusl()) { - localFileExisted = existsSync( - join(__dirname, 'y-octo.linux-riscv64-musl.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./y-octo.linux-riscv64-musl.node') - } else { - nativeBinding = require('@y-octo/node-linux-riscv64-musl') - } - } catch (e) { - loadError = e - } - } else { - localFileExisted = existsSync( - join(__dirname, 'y-octo.linux-riscv64-gnu.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./y-octo.linux-riscv64-gnu.node') - } else { - nativeBinding = require('@y-octo/node-linux-riscv64-gnu') - } - } catch (e) { - loadError = e - } - } - break - case 's390x': - localFileExisted = existsSync( - join(__dirname, 'y-octo.linux-s390x-gnu.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./y-octo.linux-s390x-gnu.node') - } else { - nativeBinding = require('@y-octo/node-linux-s390x-gnu') - } - } catch (e) { - loadError = e - } - break - default: - throw new Error(`Unsupported architecture on Linux: ${arch}`) - } - break - default: - throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`) -} - -if (!nativeBinding) { - if (loadError) { - throw loadError - } - throw new Error(`Failed to load native binding`) -} - -const { YArray, YArrayIterator, Awareness, Doc, encodeStateAsUpdate, encodeStateVector, compareStructStores, compareIds, createDeleteSetFromStructStore, equalDeleteSets, snapshot, encodeSnapshot, applyUpdate, mergeUpdates, isAbstractType, YMap, YMapEntriesIterator, YMapKeyIterator, YMapValuesIterator, YProtocol, YText, Id, Store, DeleteSet, YSnapshot } = nativeBinding - -module.exports.YArray = YArray -module.exports.YArrayIterator = YArrayIterator -module.exports.Awareness = Awareness -module.exports.Doc = Doc -module.exports.encodeStateAsUpdate = encodeStateAsUpdate -module.exports.encodeStateVector = encodeStateVector -module.exports.compareStructStores = compareStructStores -module.exports.compareIds = compareIds -module.exports.createDeleteSetFromStructStore = createDeleteSetFromStructStore -module.exports.equalDeleteSets = equalDeleteSets -module.exports.snapshot = snapshot -module.exports.encodeSnapshot = encodeSnapshot -module.exports.applyUpdate = applyUpdate -module.exports.mergeUpdates = mergeUpdates -module.exports.isAbstractType = isAbstractType -module.exports.YMap = YMap -module.exports.YMapEntriesIterator = YMapEntriesIterator -module.exports.YMapKeyIterator = YMapKeyIterator -module.exports.YMapValuesIterator = YMapValuesIterator -module.exports.YProtocol = YProtocol -module.exports.YText = YText -module.exports.Id = Id -module.exports.Store = Store -module.exports.DeleteSet = DeleteSet -module.exports.YSnapshot = YSnapshot diff --git a/yarn.lock b/yarn.lock index a2d6b83..565fa70 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12,6 +12,34 @@ __metadata: languageName: node linkType: hard +"@emnapi/core@npm:^1.1.0": + version: 1.2.0 + resolution: "@emnapi/core@npm:1.2.0" + dependencies: + "@emnapi/wasi-threads": 1.0.1 + tslib: ^2.4.0 + checksum: b3b61bd01de93346f05803151eee9dc308262065034d835db95a46842ea75867c43745c227577f19fa0542fcb3883a752477eb012bf9e4b72f540f4e23f63cbe + languageName: node + linkType: hard + +"@emnapi/runtime@npm:^1.1.0": + version: 1.2.0 + resolution: "@emnapi/runtime@npm:1.2.0" + dependencies: + tslib: ^2.4.0 + checksum: c9f5814f65a7851eda3fae96320b7ebfaf3b7e0db4e1ac2d77b55f5c0785e56b459a029413dbfc0abb1b23f059b850169888f92833150a28cdf24b9a53e535c5 + languageName: node + linkType: hard + +"@emnapi/wasi-threads@npm:1.0.1": + version: 1.0.1 + resolution: "@emnapi/wasi-threads@npm:1.0.1" + dependencies: + tslib: ^2.4.0 + checksum: e154880440ff9bfe67b417f30134f0ff6fee28913dbf4a22de2e67dda5bf5b51055647c5d1565281df17ef5dfcc89256546bdf9b8ccfd07e07566617e7ce1498 + languageName: node + linkType: hard + "@esbuild/aix-ppc64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/aix-ppc64@npm:0.21.5" @@ -19,6 +47,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/aix-ppc64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/aix-ppc64@npm:0.23.1" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + "@esbuild/android-arm64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/android-arm64@npm:0.21.5" @@ -26,6 +61,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/android-arm64@npm:0.23.1" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/android-arm@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/android-arm@npm:0.21.5" @@ -33,6 +75,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/android-arm@npm:0.23.1" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + "@esbuild/android-x64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/android-x64@npm:0.21.5" @@ -40,6 +89,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-x64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/android-x64@npm:0.23.1" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + "@esbuild/darwin-arm64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/darwin-arm64@npm:0.21.5" @@ -47,6 +103,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-arm64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/darwin-arm64@npm:0.23.1" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/darwin-x64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/darwin-x64@npm:0.21.5" @@ -54,6 +117,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-x64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/darwin-x64@npm:0.23.1" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@esbuild/freebsd-arm64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/freebsd-arm64@npm:0.21.5" @@ -61,6 +131,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-arm64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/freebsd-arm64@npm:0.23.1" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/freebsd-x64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/freebsd-x64@npm:0.21.5" @@ -68,6 +145,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-x64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/freebsd-x64@npm:0.23.1" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/linux-arm64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/linux-arm64@npm:0.21.5" @@ -75,6 +159,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/linux-arm64@npm:0.23.1" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/linux-arm@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/linux-arm@npm:0.21.5" @@ -82,6 +173,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/linux-arm@npm:0.23.1" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + "@esbuild/linux-ia32@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/linux-ia32@npm:0.21.5" @@ -89,6 +187,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ia32@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/linux-ia32@npm:0.23.1" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/linux-loong64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/linux-loong64@npm:0.21.5" @@ -96,6 +201,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-loong64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/linux-loong64@npm:0.23.1" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + "@esbuild/linux-mips64el@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/linux-mips64el@npm:0.21.5" @@ -103,6 +215,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-mips64el@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/linux-mips64el@npm:0.23.1" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + "@esbuild/linux-ppc64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/linux-ppc64@npm:0.21.5" @@ -110,6 +229,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ppc64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/linux-ppc64@npm:0.23.1" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + "@esbuild/linux-riscv64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/linux-riscv64@npm:0.21.5" @@ -117,6 +243,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-riscv64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/linux-riscv64@npm:0.23.1" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + "@esbuild/linux-s390x@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/linux-s390x@npm:0.21.5" @@ -124,6 +257,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-s390x@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/linux-s390x@npm:0.23.1" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + "@esbuild/linux-x64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/linux-x64@npm:0.21.5" @@ -131,6 +271,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-x64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/linux-x64@npm:0.23.1" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + "@esbuild/netbsd-x64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/netbsd-x64@npm:0.21.5" @@ -138,6 +285,20 @@ __metadata: languageName: node linkType: hard +"@esbuild/netbsd-x64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/netbsd-x64@npm:0.23.1" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openbsd-arm64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/openbsd-arm64@npm:0.23.1" + conditions: os=openbsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/openbsd-x64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/openbsd-x64@npm:0.21.5" @@ -145,6 +306,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/openbsd-x64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/openbsd-x64@npm:0.23.1" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/sunos-x64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/sunos-x64@npm:0.21.5" @@ -152,6 +320,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/sunos-x64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/sunos-x64@npm:0.23.1" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + "@esbuild/win32-arm64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/win32-arm64@npm:0.21.5" @@ -159,6 +334,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-arm64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/win32-arm64@npm:0.23.1" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/win32-ia32@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/win32-ia32@npm:0.21.5" @@ -166,6 +348,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-ia32@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/win32-ia32@npm:0.23.1" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/win32-x64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/win32-x64@npm:0.21.5" @@ -173,6 +362,180 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-x64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/win32-x64@npm:0.23.1" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@inquirer/checkbox@npm:^2.5.0": + version: 2.5.0 + resolution: "@inquirer/checkbox@npm:2.5.0" + dependencies: + "@inquirer/core": ^9.1.0 + "@inquirer/figures": ^1.0.5 + "@inquirer/type": ^1.5.3 + ansi-escapes: ^4.3.2 + yoctocolors-cjs: ^2.1.2 + checksum: 4f1df42fe9eb725787803b11daf11d9bda30c31c68ff170d494b089d86bcf3858a7a98f0b6edd172d20479548bcfb241a7142f5f0880d536eac8c6cb7739489e + languageName: node + linkType: hard + +"@inquirer/confirm@npm:^3.2.0": + version: 3.2.0 + resolution: "@inquirer/confirm@npm:3.2.0" + dependencies: + "@inquirer/core": ^9.1.0 + "@inquirer/type": ^1.5.3 + checksum: 6b032a26c64075dc14769558720b17f09bc6784a223bbf2c85ec42e491be6ce4c4b83518433c47e05d7e8836ba680ab1b2f6b9c553410d4326582308a1fd2259 + languageName: node + linkType: hard + +"@inquirer/core@npm:^9.1.0": + version: 9.1.0 + resolution: "@inquirer/core@npm:9.1.0" + dependencies: + "@inquirer/figures": ^1.0.5 + "@inquirer/type": ^1.5.3 + "@types/mute-stream": ^0.0.4 + "@types/node": ^22.5.2 + "@types/wrap-ansi": ^3.0.0 + ansi-escapes: ^4.3.2 + cli-spinners: ^2.9.2 + cli-width: ^4.1.0 + mute-stream: ^1.0.0 + signal-exit: ^4.1.0 + strip-ansi: ^6.0.1 + wrap-ansi: ^6.2.0 + yoctocolors-cjs: ^2.1.2 + checksum: c671d88aaecf828ad8a9fe9a897637fb0c5c7dee5840e7c8711b914dba7e0caadf61a7c515b3f3302e77e6ac95a9249ea5c29792221ccda317db7ab760485077 + languageName: node + linkType: hard + +"@inquirer/editor@npm:^2.2.0": + version: 2.2.0 + resolution: "@inquirer/editor@npm:2.2.0" + dependencies: + "@inquirer/core": ^9.1.0 + "@inquirer/type": ^1.5.3 + external-editor: ^3.1.0 + checksum: 6f238050613b4db8d4d76eafe1a3531c211a016866826bf55f1187c89d29439e69ef9fc7a4f86b26f2a4a45260184bd8200056d25c79a540014abe66581c7b81 + languageName: node + linkType: hard + +"@inquirer/expand@npm:^2.3.0": + version: 2.3.0 + resolution: "@inquirer/expand@npm:2.3.0" + dependencies: + "@inquirer/core": ^9.1.0 + "@inquirer/type": ^1.5.3 + yoctocolors-cjs: ^2.1.2 + checksum: fdc9b2d0a509e9c0120fda8e1c044593b1f4b68038fd13e19fc35cc305f1e132fddd482bc1b44966cbcab06d579d130cabbb5cac50d917549d16d7e66fad4e74 + languageName: node + linkType: hard + +"@inquirer/figures@npm:^1.0.5": + version: 1.0.5 + resolution: "@inquirer/figures@npm:1.0.5" + checksum: 01dc7b95fe7b030b0577d59f45c4fa5c002dccb43ac75ff106d7142825e09dee63a6f9c42b044da2bc964bf38c40229a112a26505a68f3912b15dc8304106bbc + languageName: node + linkType: hard + +"@inquirer/input@npm:^2.3.0": + version: 2.3.0 + resolution: "@inquirer/input@npm:2.3.0" + dependencies: + "@inquirer/core": ^9.1.0 + "@inquirer/type": ^1.5.3 + checksum: 5c5833050eb231e81beac3b70999c6a7b66b59809b0e60889dd0df012084ed2b28235da8025391ff26f41f4e913d89ead82f4fd92e18a9bdde5f2465416080ae + languageName: node + linkType: hard + +"@inquirer/number@npm:^1.1.0": + version: 1.1.0 + resolution: "@inquirer/number@npm:1.1.0" + dependencies: + "@inquirer/core": ^9.1.0 + "@inquirer/type": ^1.5.3 + checksum: 5f56fd32490b35f6fe53032db19bcad5f5000c3ed88e27e58f0d9dfe4f858808d7b9447b5a3a1d85216b2a4293f2d97c51624abf87d06ac26da45b50485b61db + languageName: node + linkType: hard + +"@inquirer/password@npm:^2.2.0": + version: 2.2.0 + resolution: "@inquirer/password@npm:2.2.0" + dependencies: + "@inquirer/core": ^9.1.0 + "@inquirer/type": ^1.5.3 + ansi-escapes: ^4.3.2 + checksum: 85a15957a667fcfcbfbd36b1acaf31d5c23473f229cb03ade37adfffa2016cfe75d81b0c59cf697e42bfe7dd2fa4f8e400b7eeafbda711fb2785456520a939f8 + languageName: node + linkType: hard + +"@inquirer/prompts@npm:^5.5.0": + version: 5.5.0 + resolution: "@inquirer/prompts@npm:5.5.0" + dependencies: + "@inquirer/checkbox": ^2.5.0 + "@inquirer/confirm": ^3.2.0 + "@inquirer/editor": ^2.2.0 + "@inquirer/expand": ^2.3.0 + "@inquirer/input": ^2.3.0 + "@inquirer/number": ^1.1.0 + "@inquirer/password": ^2.2.0 + "@inquirer/rawlist": ^2.3.0 + "@inquirer/search": ^1.1.0 + "@inquirer/select": ^2.5.0 + checksum: 647312c644284223483ec67fa476fee7bcc099793fb6773e44f325554c91f7db7a124dd81f0b5a9ad22b07c9e807c9f276b34ee4614c99921a9644b9e831d4e9 + languageName: node + linkType: hard + +"@inquirer/rawlist@npm:^2.3.0": + version: 2.3.0 + resolution: "@inquirer/rawlist@npm:2.3.0" + dependencies: + "@inquirer/core": ^9.1.0 + "@inquirer/type": ^1.5.3 + yoctocolors-cjs: ^2.1.2 + checksum: 48635c5f62527dde46209d168da53e364c33ea7f795c23d9a5ee7e0836159404adc4f68e7a743a0a9a9c4c90fc1f96668b6ff869c3232f1a3b585d3afcf0b702 + languageName: node + linkType: hard + +"@inquirer/search@npm:^1.1.0": + version: 1.1.0 + resolution: "@inquirer/search@npm:1.1.0" + dependencies: + "@inquirer/core": ^9.1.0 + "@inquirer/figures": ^1.0.5 + "@inquirer/type": ^1.5.3 + yoctocolors-cjs: ^2.1.2 + checksum: 1304adf79f181941c4bc41e7a400e68e3d9281819d523b0efb04f928f96ab0f28cdf800236c6847757a02a958ee3b0047d94079aba04a0aececfd9968f656c01 + languageName: node + linkType: hard + +"@inquirer/select@npm:^2.5.0": + version: 2.5.0 + resolution: "@inquirer/select@npm:2.5.0" + dependencies: + "@inquirer/core": ^9.1.0 + "@inquirer/figures": ^1.0.5 + "@inquirer/type": ^1.5.3 + ansi-escapes: ^4.3.2 + yoctocolors-cjs: ^2.1.2 + checksum: fc7aa1a70d69f61ad2270ebfb83c059ba8d6bc749a90ae759a538acfa0ae158621f9653625f3fa7ac81072674f19013898e16598ce71a3dd2476897304832598 + languageName: node + linkType: hard + +"@inquirer/type@npm:^1.5.3": + version: 1.5.3 + resolution: "@inquirer/type@npm:1.5.3" + dependencies: + mute-stream: ^1.0.0 + checksum: 12ca8437b1c60fab52b84b27360a7715106bcab4514f7f1be017ccf50c5c027697828ccfaeccd083c71fcd59187b406fa31d414715e016a07c7dc439cb546fbc + languageName: node + linkType: hard + "@isaacs/cliui@npm:^8.0.2": version: 8.0.2 resolution: "@isaacs/cliui@npm:8.0.2" @@ -208,6 +571,13 @@ __metadata: languageName: node linkType: hard +"@jridgewell/sourcemap-codec@npm:^1.5.0": + version: 1.5.0 + resolution: "@jridgewell/sourcemap-codec@npm:1.5.0" + checksum: 05df4f2538b3b0f998ea4c1cd34574d0feba216fa5d4ccaef0187d12abf82eafe6021cec8b49f9bb4d90f2ba4582ccc581e72986a5fcf4176ae0cfeb04cf52ec + languageName: node + linkType: hard + "@jridgewell/trace-mapping@npm:^0.3.12": version: 0.3.19 resolution: "@jridgewell/trace-mapping@npm:0.3.19" @@ -237,88 +607,965 @@ __metadata: languageName: node linkType: hard -"@napi-rs/cli@npm:^2.18.4": - version: 2.18.4 - resolution: "@napi-rs/cli@npm:2.18.4" +"@napi-rs/cli@npm:^3.0.0-alpha.62": + version: 3.0.0-alpha.62 + resolution: "@napi-rs/cli@npm:3.0.0-alpha.62" + dependencies: + "@napi-rs/cross-toolchain": ^0.0.16 + "@napi-rs/wasm-tools": ^0.0.2 + "@octokit/rest": ^21.0.0 + clipanion: ^3.2.1 + colorette: ^2.0.20 + debug: ^4.3.4 + emnapi: ^1.2.0 + inquirer: ^10.0.0 + js-yaml: ^4.1.0 + lodash-es: ^4.17.21 + semver: ^7.5.4 + toml: ^3.0.0 + typanion: ^3.14.0 + wasm-sjlj: ^1.0.5 + peerDependencies: + "@emnapi/runtime": ^1.1.0 + emnapi: ^1.1.0 + peerDependenciesMeta: + "@emnapi/runtime": + optional: true + emnapi: + optional: true bin: - napi: scripts/index.js - checksum: f243e5c822a4a9103fba49193eda2023cb08c1ef9c0b521d8a0ece860ef13f9f2b4d5ac106c3d9e5792d22ed2196213e336cf59d9c2ee81241ac4b4b8ca6ea30 + napi: ./dist/cli.js + napi-raw: ./cli.mjs + checksum: 350e5181922449e38dfde5d0565c65ad5a021ae87681e126ae1174692d22fc64cfc64602078e95574157d9f5a34cefdceddb2da8025e0d8bd702f4091343d599 languageName: node linkType: hard -"@nodelib/fs.scandir@npm:2.1.5": - version: 2.1.5 - resolution: "@nodelib/fs.scandir@npm:2.1.5" +"@napi-rs/cross-toolchain@npm:^0.0.16": + version: 0.0.16 + resolution: "@napi-rs/cross-toolchain@npm:0.0.16" dependencies: - "@nodelib/fs.stat": 2.0.5 - run-parallel: ^1.1.9 - checksum: a970d595bd23c66c880e0ef1817791432dbb7acbb8d44b7e7d0e7a22f4521260d4a83f7f9fd61d44fda4610105577f8f58a60718105fb38352baed612fd79e59 + "@napi-rs/lzma": ^1.3.1 + "@napi-rs/tar": ^0.1.1 + debug: ^4.3.4 + peerDependencies: + "@napi-rs/cross-toolchain-arm64-target-aarch64": ^0.0.16 + "@napi-rs/cross-toolchain-arm64-target-armv7": ^0.0.16 + "@napi-rs/cross-toolchain-arm64-target-x86_64": ^0.0.16 + "@napi-rs/cross-toolchain-x64-target-aarch64": ^0.0.16 + "@napi-rs/cross-toolchain-x64-target-armv7": ^0.0.16 + "@napi-rs/cross-toolchain-x64-target-x86_64": ^0.0.16 + peerDependenciesMeta: + "@napi-rs/cross-toolchain-arm64-target-aarch64": + optional: true + "@napi-rs/cross-toolchain-arm64-target-armv7": + optional: true + "@napi-rs/cross-toolchain-arm64-target-x86_64": + optional: true + "@napi-rs/cross-toolchain-x64-target-aarch64": + optional: true + "@napi-rs/cross-toolchain-x64-target-armv7": + optional: true + "@napi-rs/cross-toolchain-x64-target-x86_64": + optional: true + checksum: 8a0e75b10fad368ba0b04f09281a42aa2cd3bdc880ceffa7671c099418635fccca1d578ac141bca09810ddf23681907965d2df5686f5e79991cac1f02b44e398 languageName: node linkType: hard -"@nodelib/fs.stat@npm:2.0.5, @nodelib/fs.stat@npm:^2.0.2": - version: 2.0.5 - resolution: "@nodelib/fs.stat@npm:2.0.5" - checksum: 012480b5ca9d97bff9261571dbbec7bbc6033f69cc92908bc1ecfad0792361a5a1994bc48674b9ef76419d056a03efadfce5a6cf6dbc0a36559571a7a483f6f0 +"@napi-rs/lzma-android-arm-eabi@npm:1.3.1": + version: 1.3.1 + resolution: "@napi-rs/lzma-android-arm-eabi@npm:1.3.1" + conditions: os=android & cpu=arm languageName: node linkType: hard -"@nodelib/fs.walk@npm:^1.2.3": - version: 1.2.8 - resolution: "@nodelib/fs.walk@npm:1.2.8" - dependencies: - "@nodelib/fs.scandir": 2.1.5 - fastq: ^1.6.0 - checksum: 190c643f156d8f8f277bf2a6078af1ffde1fd43f498f187c2db24d35b4b4b5785c02c7dc52e356497b9a1b65b13edc996de08de0b961c32844364da02986dc53 +"@napi-rs/lzma-android-arm64@npm:1.3.1": + version: 1.3.1 + resolution: "@napi-rs/lzma-android-arm64@npm:1.3.1" + conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@nolyfill/shared@npm:1.0.28": - version: 1.0.28 - resolution: "@nolyfill/shared@npm:1.0.28" - checksum: 395261f73688e2a58f78c34238a13176feb2b79b0a331d9bcf6bfdb9d8473115934468e73eefe89614a0e5cb0b896d900c0e738de452fbb4d3648c944db02428 +"@napi-rs/lzma-darwin-arm64@npm:1.3.1": + version: 1.3.1 + resolution: "@napi-rs/lzma-darwin-arm64@npm:1.3.1" + conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@npmcli/agent@npm:^2.0.0": - version: 2.2.2 - resolution: "@npmcli/agent@npm:2.2.2" - dependencies: - agent-base: ^7.1.0 - http-proxy-agent: ^7.0.0 - https-proxy-agent: ^7.0.1 - lru-cache: ^10.0.1 - socks-proxy-agent: ^8.0.3 - checksum: 67de7b88cc627a79743c88bab35e023e23daf13831a8aa4e15f998b92f5507b644d8ffc3788afc8e64423c612e0785a6a92b74782ce368f49a6746084b50d874 +"@napi-rs/lzma-darwin-x64@npm:1.3.1": + version: 1.3.1 + resolution: "@napi-rs/lzma-darwin-x64@npm:1.3.1" + conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@npmcli/fs@npm:^3.1.0": - version: 3.1.1 - resolution: "@npmcli/fs@npm:3.1.1" - dependencies: - semver: ^7.3.5 - checksum: d960cab4b93adcb31ce223bfb75c5714edbd55747342efb67dcc2f25e023d930a7af6ece3e75f2f459b6f38fc14d031c766f116cd124fdc937fd33112579e820 +"@napi-rs/lzma-freebsd-x64@npm:1.3.1": + version: 1.3.1 + resolution: "@napi-rs/lzma-freebsd-x64@npm:1.3.1" + conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@pkgjs/parseargs@npm:^0.11.0": - version: 0.11.0 - resolution: "@pkgjs/parseargs@npm:0.11.0" - checksum: 6ad6a00fc4f2f2cfc6bff76fb1d88b8ee20bc0601e18ebb01b6d4be583733a860239a521a7fbca73b612e66705078809483549d2b18f370eb346c5155c8e4a0f +"@napi-rs/lzma-linux-arm-gnueabihf@npm:1.3.1": + version: 1.3.1 + resolution: "@napi-rs/lzma-linux-arm-gnueabihf@npm:1.3.1" + conditions: os=linux & cpu=arm languageName: node linkType: hard -"@rollup/pluginutils@npm:^4.0.0": - version: 4.2.1 - resolution: "@rollup/pluginutils@npm:4.2.1" - dependencies: - estree-walker: ^2.0.1 +"@napi-rs/lzma-linux-arm64-gnu@npm:1.3.1": + version: 1.3.1 + resolution: "@napi-rs/lzma-linux-arm64-gnu@npm:1.3.1" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@napi-rs/lzma-linux-arm64-musl@npm:1.3.1": + version: 1.3.1 + resolution: "@napi-rs/lzma-linux-arm64-musl@npm:1.3.1" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@napi-rs/lzma-linux-x64-gnu@npm:1.3.1": + version: 1.3.1 + resolution: "@napi-rs/lzma-linux-x64-gnu@npm:1.3.1" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@napi-rs/lzma-linux-x64-musl@npm:1.3.1": + version: 1.3.1 + resolution: "@napi-rs/lzma-linux-x64-musl@npm:1.3.1" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@napi-rs/lzma-wasm32-wasi@npm:1.3.1": + version: 1.3.1 + resolution: "@napi-rs/lzma-wasm32-wasi@npm:1.3.1" + dependencies: + "@napi-rs/wasm-runtime": ^0.2.3 + conditions: cpu=wasm32 + languageName: node + linkType: hard + +"@napi-rs/lzma-win32-arm64-msvc@npm:1.3.1": + version: 1.3.1 + resolution: "@napi-rs/lzma-win32-arm64-msvc@npm:1.3.1" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@napi-rs/lzma-win32-ia32-msvc@npm:1.3.1": + version: 1.3.1 + resolution: "@napi-rs/lzma-win32-ia32-msvc@npm:1.3.1" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@napi-rs/lzma-win32-x64-msvc@npm:1.3.1": + version: 1.3.1 + resolution: "@napi-rs/lzma-win32-x64-msvc@npm:1.3.1" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@napi-rs/lzma@npm:^1.3.1": + version: 1.3.1 + resolution: "@napi-rs/lzma@npm:1.3.1" + dependencies: + "@napi-rs/lzma-android-arm-eabi": 1.3.1 + "@napi-rs/lzma-android-arm64": 1.3.1 + "@napi-rs/lzma-darwin-arm64": 1.3.1 + "@napi-rs/lzma-darwin-x64": 1.3.1 + "@napi-rs/lzma-freebsd-x64": 1.3.1 + "@napi-rs/lzma-linux-arm-gnueabihf": 1.3.1 + "@napi-rs/lzma-linux-arm64-gnu": 1.3.1 + "@napi-rs/lzma-linux-arm64-musl": 1.3.1 + "@napi-rs/lzma-linux-x64-gnu": 1.3.1 + "@napi-rs/lzma-linux-x64-musl": 1.3.1 + "@napi-rs/lzma-wasm32-wasi": 1.3.1 + "@napi-rs/lzma-win32-arm64-msvc": 1.3.1 + "@napi-rs/lzma-win32-ia32-msvc": 1.3.1 + "@napi-rs/lzma-win32-x64-msvc": 1.3.1 + dependenciesMeta: + "@napi-rs/lzma-android-arm-eabi": + optional: true + "@napi-rs/lzma-android-arm64": + optional: true + "@napi-rs/lzma-darwin-arm64": + optional: true + "@napi-rs/lzma-darwin-x64": + optional: true + "@napi-rs/lzma-freebsd-x64": + optional: true + "@napi-rs/lzma-linux-arm-gnueabihf": + optional: true + "@napi-rs/lzma-linux-arm64-gnu": + optional: true + "@napi-rs/lzma-linux-arm64-musl": + optional: true + "@napi-rs/lzma-linux-x64-gnu": + optional: true + "@napi-rs/lzma-linux-x64-musl": + optional: true + "@napi-rs/lzma-wasm32-wasi": + optional: true + "@napi-rs/lzma-win32-arm64-msvc": + optional: true + "@napi-rs/lzma-win32-ia32-msvc": + optional: true + "@napi-rs/lzma-win32-x64-msvc": + optional: true + checksum: fc9d7075b95cbb59e0f0f645174f64b3abe8af69483a721a4466c88c9daae2738928da98cd546967cc308fc4da9ca4c9f0908c3d9bffde35cce46f12cf81a80d + languageName: node + linkType: hard + +"@napi-rs/tar-android-arm-eabi@npm:0.1.4": + version: 0.1.4 + resolution: "@napi-rs/tar-android-arm-eabi@npm:0.1.4" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@napi-rs/tar-android-arm64@npm:0.1.4": + version: 0.1.4 + resolution: "@napi-rs/tar-android-arm64@npm:0.1.4" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@napi-rs/tar-darwin-arm64@npm:0.1.4": + version: 0.1.4 + resolution: "@napi-rs/tar-darwin-arm64@npm:0.1.4" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@napi-rs/tar-darwin-x64@npm:0.1.4": + version: 0.1.4 + resolution: "@napi-rs/tar-darwin-x64@npm:0.1.4" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@napi-rs/tar-freebsd-x64@npm:0.1.4": + version: 0.1.4 + resolution: "@napi-rs/tar-freebsd-x64@npm:0.1.4" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@napi-rs/tar-linux-arm-gnueabihf@npm:0.1.4": + version: 0.1.4 + resolution: "@napi-rs/tar-linux-arm-gnueabihf@npm:0.1.4" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@napi-rs/tar-linux-arm64-gnu@npm:0.1.4": + version: 0.1.4 + resolution: "@napi-rs/tar-linux-arm64-gnu@npm:0.1.4" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@napi-rs/tar-linux-arm64-musl@npm:0.1.4": + version: 0.1.4 + resolution: "@napi-rs/tar-linux-arm64-musl@npm:0.1.4" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@napi-rs/tar-linux-ppc64-gnu@npm:0.1.4": + version: 0.1.4 + resolution: "@napi-rs/tar-linux-ppc64-gnu@npm:0.1.4" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@napi-rs/tar-linux-s390x-gnu@npm:0.1.4": + version: 0.1.4 + resolution: "@napi-rs/tar-linux-s390x-gnu@npm:0.1.4" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@napi-rs/tar-linux-x64-gnu@npm:0.1.4": + version: 0.1.4 + resolution: "@napi-rs/tar-linux-x64-gnu@npm:0.1.4" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@napi-rs/tar-linux-x64-musl@npm:0.1.4": + version: 0.1.4 + resolution: "@napi-rs/tar-linux-x64-musl@npm:0.1.4" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@napi-rs/tar-wasm32-wasi@npm:0.1.4": + version: 0.1.4 + resolution: "@napi-rs/tar-wasm32-wasi@npm:0.1.4" + dependencies: + "@napi-rs/wasm-runtime": ^0.2.4 + conditions: cpu=wasm32 + languageName: node + linkType: hard + +"@napi-rs/tar-win32-arm64-msvc@npm:0.1.4": + version: 0.1.4 + resolution: "@napi-rs/tar-win32-arm64-msvc@npm:0.1.4" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@napi-rs/tar-win32-ia32-msvc@npm:0.1.4": + version: 0.1.4 + resolution: "@napi-rs/tar-win32-ia32-msvc@npm:0.1.4" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@napi-rs/tar-win32-x64-msvc@npm:0.1.4": + version: 0.1.4 + resolution: "@napi-rs/tar-win32-x64-msvc@npm:0.1.4" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@napi-rs/tar@npm:^0.1.1": + version: 0.1.4 + resolution: "@napi-rs/tar@npm:0.1.4" + dependencies: + "@napi-rs/tar-android-arm-eabi": 0.1.4 + "@napi-rs/tar-android-arm64": 0.1.4 + "@napi-rs/tar-darwin-arm64": 0.1.4 + "@napi-rs/tar-darwin-x64": 0.1.4 + "@napi-rs/tar-freebsd-x64": 0.1.4 + "@napi-rs/tar-linux-arm-gnueabihf": 0.1.4 + "@napi-rs/tar-linux-arm64-gnu": 0.1.4 + "@napi-rs/tar-linux-arm64-musl": 0.1.4 + "@napi-rs/tar-linux-ppc64-gnu": 0.1.4 + "@napi-rs/tar-linux-s390x-gnu": 0.1.4 + "@napi-rs/tar-linux-x64-gnu": 0.1.4 + "@napi-rs/tar-linux-x64-musl": 0.1.4 + "@napi-rs/tar-wasm32-wasi": 0.1.4 + "@napi-rs/tar-win32-arm64-msvc": 0.1.4 + "@napi-rs/tar-win32-ia32-msvc": 0.1.4 + "@napi-rs/tar-win32-x64-msvc": 0.1.4 + dependenciesMeta: + "@napi-rs/tar-android-arm-eabi": + optional: true + "@napi-rs/tar-android-arm64": + optional: true + "@napi-rs/tar-darwin-arm64": + optional: true + "@napi-rs/tar-darwin-x64": + optional: true + "@napi-rs/tar-freebsd-x64": + optional: true + "@napi-rs/tar-linux-arm-gnueabihf": + optional: true + "@napi-rs/tar-linux-arm64-gnu": + optional: true + "@napi-rs/tar-linux-arm64-musl": + optional: true + "@napi-rs/tar-linux-ppc64-gnu": + optional: true + "@napi-rs/tar-linux-s390x-gnu": + optional: true + "@napi-rs/tar-linux-x64-gnu": + optional: true + "@napi-rs/tar-linux-x64-musl": + optional: true + "@napi-rs/tar-wasm32-wasi": + optional: true + "@napi-rs/tar-win32-arm64-msvc": + optional: true + "@napi-rs/tar-win32-ia32-msvc": + optional: true + "@napi-rs/tar-win32-x64-msvc": + optional: true + checksum: c94548e1784135ab4271ce970b054cfbe3b87014c6e51386298028616d59127fbec8d478b3b058548eb3ddca96027e24b144a18c535468e0300b5a3ae1cde8ae + languageName: node + linkType: hard + +"@napi-rs/wasm-runtime@npm:^0.2.3, @napi-rs/wasm-runtime@npm:^0.2.4": + version: 0.2.4 + resolution: "@napi-rs/wasm-runtime@npm:0.2.4" + dependencies: + "@emnapi/core": ^1.1.0 + "@emnapi/runtime": ^1.1.0 + "@tybys/wasm-util": ^0.9.0 + checksum: 976eeca9c411724bf004f92a94707f1c78b6a5932a354e8b456eaae16c476dd6b96244c4afec60a3f621c922fca3ef2c6c3f6a900bd6b79f509dd4c0c2b3376d + languageName: node + linkType: hard + +"@napi-rs/wasm-tools-android-arm-eabi@npm:0.0.2": + version: 0.0.2 + resolution: "@napi-rs/wasm-tools-android-arm-eabi@npm:0.0.2" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@napi-rs/wasm-tools-android-arm64@npm:0.0.2": + version: 0.0.2 + resolution: "@napi-rs/wasm-tools-android-arm64@npm:0.0.2" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@napi-rs/wasm-tools-darwin-arm64@npm:0.0.2": + version: 0.0.2 + resolution: "@napi-rs/wasm-tools-darwin-arm64@npm:0.0.2" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@napi-rs/wasm-tools-darwin-x64@npm:0.0.2": + version: 0.0.2 + resolution: "@napi-rs/wasm-tools-darwin-x64@npm:0.0.2" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@napi-rs/wasm-tools-freebsd-x64@npm:0.0.2": + version: 0.0.2 + resolution: "@napi-rs/wasm-tools-freebsd-x64@npm:0.0.2" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@napi-rs/wasm-tools-linux-arm64-gnu@npm:0.0.2": + version: 0.0.2 + resolution: "@napi-rs/wasm-tools-linux-arm64-gnu@npm:0.0.2" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@napi-rs/wasm-tools-linux-arm64-musl@npm:0.0.2": + version: 0.0.2 + resolution: "@napi-rs/wasm-tools-linux-arm64-musl@npm:0.0.2" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@napi-rs/wasm-tools-linux-x64-gnu@npm:0.0.2": + version: 0.0.2 + resolution: "@napi-rs/wasm-tools-linux-x64-gnu@npm:0.0.2" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@napi-rs/wasm-tools-linux-x64-musl@npm:0.0.2": + version: 0.0.2 + resolution: "@napi-rs/wasm-tools-linux-x64-musl@npm:0.0.2" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@napi-rs/wasm-tools-wasm32-wasi@npm:0.0.2": + version: 0.0.2 + resolution: "@napi-rs/wasm-tools-wasm32-wasi@npm:0.0.2" + dependencies: + "@napi-rs/wasm-runtime": ^0.2.3 + conditions: cpu=wasm32 + languageName: node + linkType: hard + +"@napi-rs/wasm-tools-win32-arm64-msvc@npm:0.0.2": + version: 0.0.2 + resolution: "@napi-rs/wasm-tools-win32-arm64-msvc@npm:0.0.2" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@napi-rs/wasm-tools-win32-ia32-msvc@npm:0.0.2": + version: 0.0.2 + resolution: "@napi-rs/wasm-tools-win32-ia32-msvc@npm:0.0.2" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@napi-rs/wasm-tools-win32-x64-msvc@npm:0.0.2": + version: 0.0.2 + resolution: "@napi-rs/wasm-tools-win32-x64-msvc@npm:0.0.2" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@napi-rs/wasm-tools@npm:^0.0.2": + version: 0.0.2 + resolution: "@napi-rs/wasm-tools@npm:0.0.2" + dependencies: + "@napi-rs/wasm-tools-android-arm-eabi": 0.0.2 + "@napi-rs/wasm-tools-android-arm64": 0.0.2 + "@napi-rs/wasm-tools-darwin-arm64": 0.0.2 + "@napi-rs/wasm-tools-darwin-x64": 0.0.2 + "@napi-rs/wasm-tools-freebsd-x64": 0.0.2 + "@napi-rs/wasm-tools-linux-arm64-gnu": 0.0.2 + "@napi-rs/wasm-tools-linux-arm64-musl": 0.0.2 + "@napi-rs/wasm-tools-linux-x64-gnu": 0.0.2 + "@napi-rs/wasm-tools-linux-x64-musl": 0.0.2 + "@napi-rs/wasm-tools-wasm32-wasi": 0.0.2 + "@napi-rs/wasm-tools-win32-arm64-msvc": 0.0.2 + "@napi-rs/wasm-tools-win32-ia32-msvc": 0.0.2 + "@napi-rs/wasm-tools-win32-x64-msvc": 0.0.2 + dependenciesMeta: + "@napi-rs/wasm-tools-android-arm-eabi": + optional: true + "@napi-rs/wasm-tools-android-arm64": + optional: true + "@napi-rs/wasm-tools-darwin-arm64": + optional: true + "@napi-rs/wasm-tools-darwin-x64": + optional: true + "@napi-rs/wasm-tools-freebsd-x64": + optional: true + "@napi-rs/wasm-tools-linux-arm64-gnu": + optional: true + "@napi-rs/wasm-tools-linux-arm64-musl": + optional: true + "@napi-rs/wasm-tools-linux-x64-gnu": + optional: true + "@napi-rs/wasm-tools-linux-x64-musl": + optional: true + "@napi-rs/wasm-tools-wasm32-wasi": + optional: true + "@napi-rs/wasm-tools-win32-arm64-msvc": + optional: true + "@napi-rs/wasm-tools-win32-ia32-msvc": + optional: true + "@napi-rs/wasm-tools-win32-x64-msvc": + optional: true + checksum: 5ef364f487353537b13006c222c1a8ee3b85e07227189c9bcd1d707152489c607aa91a69cff1484e52f6a23fbb3db1e188aa9d8a97a9d40486aa17deaf190738 + languageName: node + linkType: hard + +"@nodelib/fs.scandir@npm:2.1.5": + version: 2.1.5 + resolution: "@nodelib/fs.scandir@npm:2.1.5" + dependencies: + "@nodelib/fs.stat": 2.0.5 + run-parallel: ^1.1.9 + checksum: a970d595bd23c66c880e0ef1817791432dbb7acbb8d44b7e7d0e7a22f4521260d4a83f7f9fd61d44fda4610105577f8f58a60718105fb38352baed612fd79e59 + languageName: node + linkType: hard + +"@nodelib/fs.stat@npm:2.0.5, @nodelib/fs.stat@npm:^2.0.2": + version: 2.0.5 + resolution: "@nodelib/fs.stat@npm:2.0.5" + checksum: 012480b5ca9d97bff9261571dbbec7bbc6033f69cc92908bc1ecfad0792361a5a1994bc48674b9ef76419d056a03efadfce5a6cf6dbc0a36559571a7a483f6f0 + languageName: node + linkType: hard + +"@nodelib/fs.walk@npm:^1.2.3": + version: 1.2.8 + resolution: "@nodelib/fs.walk@npm:1.2.8" + dependencies: + "@nodelib/fs.scandir": 2.1.5 + fastq: ^1.6.0 + checksum: 190c643f156d8f8f277bf2a6078af1ffde1fd43f498f187c2db24d35b4b4b5785c02c7dc52e356497b9a1b65b13edc996de08de0b961c32844364da02986dc53 + languageName: node + linkType: hard + +"@nolyfill/shared@npm:1.0.28": + version: 1.0.28 + resolution: "@nolyfill/shared@npm:1.0.28" + checksum: 395261f73688e2a58f78c34238a13176feb2b79b0a331d9bcf6bfdb9d8473115934468e73eefe89614a0e5cb0b896d900c0e738de452fbb4d3648c944db02428 + languageName: node + linkType: hard + +"@npmcli/agent@npm:^2.0.0": + version: 2.2.2 + resolution: "@npmcli/agent@npm:2.2.2" + dependencies: + agent-base: ^7.1.0 + http-proxy-agent: ^7.0.0 + https-proxy-agent: ^7.0.1 + lru-cache: ^10.0.1 + socks-proxy-agent: ^8.0.3 + checksum: 67de7b88cc627a79743c88bab35e023e23daf13831a8aa4e15f998b92f5507b644d8ffc3788afc8e64423c612e0785a6a92b74782ce368f49a6746084b50d874 + languageName: node + linkType: hard + +"@npmcli/fs@npm:^3.1.0": + version: 3.1.1 + resolution: "@npmcli/fs@npm:3.1.1" + dependencies: + semver: ^7.3.5 + checksum: d960cab4b93adcb31ce223bfb75c5714edbd55747342efb67dcc2f25e023d930a7af6ece3e75f2f459b6f38fc14d031c766f116cd124fdc937fd33112579e820 + languageName: node + linkType: hard + +"@octokit/auth-token@npm:^5.0.0": + version: 5.1.1 + resolution: "@octokit/auth-token@npm:5.1.1" + checksum: b39516dda44aeced0326227c53aade621effe1d59c4b0f48ebe2b9fd32b5156e02705bcb2fb1bf48b11f26cc6aff1a0683c32c3d5424e0118dae6596e431d489 + languageName: node + linkType: hard + +"@octokit/core@npm:^6.1.2": + version: 6.1.2 + resolution: "@octokit/core@npm:6.1.2" + dependencies: + "@octokit/auth-token": ^5.0.0 + "@octokit/graphql": ^8.0.0 + "@octokit/request": ^9.0.0 + "@octokit/request-error": ^6.0.1 + "@octokit/types": ^13.0.0 + before-after-hook: ^3.0.2 + universal-user-agent: ^7.0.0 + checksum: e794fb11b3942f55033f4cf6c0914953fd974587309498e8709c428660fa5c098334d83af5e41457dbe67d92d70a8b559c6cc00457d6c95290fa6c9e1d4bfc42 + languageName: node + linkType: hard + +"@octokit/endpoint@npm:^10.0.0": + version: 10.1.1 + resolution: "@octokit/endpoint@npm:10.1.1" + dependencies: + "@octokit/types": ^13.0.0 + universal-user-agent: ^7.0.2 + checksum: fde158f40dc9a88e92a8ac1d347a54599aa5715ec24045be9cb8ff8decb3c17b63c91eca1bab12dfe0e0cd37433127dd05cd05db14a719dca749bc56093aa915 + languageName: node + linkType: hard + +"@octokit/graphql@npm:^8.0.0": + version: 8.1.1 + resolution: "@octokit/graphql@npm:8.1.1" + dependencies: + "@octokit/request": ^9.0.0 + "@octokit/types": ^13.0.0 + universal-user-agent: ^7.0.0 + checksum: 07239666b0ca38a7d8c581570b544ee9fd1a2616c8dd436af31879662b3345c44ed52e3d7b311840a1c5772a23f02caf7585aca56f36e50f38f0207a87577a9c + languageName: node + linkType: hard + +"@octokit/openapi-types@npm:^22.2.0": + version: 22.2.0 + resolution: "@octokit/openapi-types@npm:22.2.0" + checksum: eca41feac2b83298e0d95e253ac1c5b6d65155ac57f65c5fd8d4a485d9728922d85ff4bee0e815a1f3a5421311db092bdb6da9d6104a1b1843d8b274bcad9630 + languageName: node + linkType: hard + +"@octokit/plugin-paginate-rest@npm:^11.0.0": + version: 11.3.3 + resolution: "@octokit/plugin-paginate-rest@npm:11.3.3" + dependencies: + "@octokit/types": ^13.5.0 + peerDependencies: + "@octokit/core": ">=6" + checksum: 93c7993562caed67b67f75aa77ffb10d032c242a70e9380e2fb9ab67dd2fb84d420231d09cd8a64f1553ffd325f3ef8c640c62e4267b7f3b352b16d4d5e11ef6 + languageName: node + linkType: hard + +"@octokit/plugin-request-log@npm:^5.3.1": + version: 5.3.1 + resolution: "@octokit/plugin-request-log@npm:5.3.1" + peerDependencies: + "@octokit/core": ">=6" + checksum: a27e163282c8d0ba8feee4d3cbbd1b62e1aa89a892877f7a9876fc17ddde3e1e1af922e6664221a0cabae99b8a7a2a5215b9ec2ee5222edb50e06298e99022b0 + languageName: node + linkType: hard + +"@octokit/plugin-rest-endpoint-methods@npm:^13.0.0": + version: 13.2.4 + resolution: "@octokit/plugin-rest-endpoint-methods@npm:13.2.4" + dependencies: + "@octokit/types": ^13.5.0 + peerDependencies: + "@octokit/core": ">=6" + checksum: 149643bf98933af92003c55ad7f1e87c239941e843708cfc7389d378e85069e88b7cccaf8227469ee037d54da93cbdb881a34ce9888f5a60f89c689305eb5730 + languageName: node + linkType: hard + +"@octokit/request-error@npm:^6.0.1": + version: 6.1.4 + resolution: "@octokit/request-error@npm:6.1.4" + dependencies: + "@octokit/types": ^13.0.0 + checksum: e4e475ec50cef8e271f39e69667d0f8eaccb2367aa56b81638c629b5bbfa2b697b40207301e5c797a63051a82d8698e7c792b4050b84e383c54300a49a01304a + languageName: node + linkType: hard + +"@octokit/request@npm:^9.0.0": + version: 9.1.3 + resolution: "@octokit/request@npm:9.1.3" + dependencies: + "@octokit/endpoint": ^10.0.0 + "@octokit/request-error": ^6.0.1 + "@octokit/types": ^13.1.0 + universal-user-agent: ^7.0.2 + checksum: 0a1c1a4f9ba67954402ef6d1e3d8e78518487750f3a31c100133840fff393ed9cc29533282914adf0731f7cc880a2778b8a6ac81527b376a278360a86e79597d + languageName: node + linkType: hard + +"@octokit/rest@npm:^21.0.0": + version: 21.0.2 + resolution: "@octokit/rest@npm:21.0.2" + dependencies: + "@octokit/core": ^6.1.2 + "@octokit/plugin-paginate-rest": ^11.0.0 + "@octokit/plugin-request-log": ^5.3.1 + "@octokit/plugin-rest-endpoint-methods": ^13.0.0 + checksum: 81dc98bbc27d4891a211628ea49ba40f087f986ee85d7e2f0579b66e4046dd6b6d63ffeb0eb011c9240dd61906798795e4b9e309af230f31df0a42db79ae20bc + languageName: node + linkType: hard + +"@octokit/types@npm:^13.0.0, @octokit/types@npm:^13.1.0, @octokit/types@npm:^13.5.0": + version: 13.5.0 + resolution: "@octokit/types@npm:13.5.0" + dependencies: + "@octokit/openapi-types": ^22.2.0 + checksum: 8e92f2b145b3c28a35312f93714245824a7b6b7353caa88edfdc85fc2ed4108321ed0c3988001ea53449fbb212febe0e8e9582744e85c3574dabe9d0441af5a0 + languageName: node + linkType: hard + +"@pkgjs/parseargs@npm:^0.11.0": + version: 0.11.0 + resolution: "@pkgjs/parseargs@npm:0.11.0" + checksum: 6ad6a00fc4f2f2cfc6bff76fb1d88b8ee20bc0601e18ebb01b6d4be583733a860239a521a7fbca73b612e66705078809483549d2b18f370eb346c5155c8e4a0f + languageName: node + linkType: hard + +"@rollup/plugin-alias@npm:^5.1.0": + version: 5.1.0 + resolution: "@rollup/plugin-alias@npm:5.1.0" + dependencies: + slash: ^4.0.0 + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + checksum: e9f0a27b0f6f166c4c72757a2ecf23411dcc6da22ae2e020ddf30fa95526c8ab36ad372ed994dde806de4dcc47b2f1305138b953764a8f879c85fd725ac2a493 + languageName: node + linkType: hard + +"@rollup/plugin-commonjs@npm:^26.0.1": + version: 26.0.1 + resolution: "@rollup/plugin-commonjs@npm:26.0.1" + dependencies: + "@rollup/pluginutils": ^5.0.1 + commondir: ^1.0.1 + estree-walker: ^2.0.2 + glob: ^10.4.1 + is-reference: 1.2.1 + magic-string: ^0.30.3 + peerDependencies: + rollup: ^2.68.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + checksum: 88d1349cc2cda4ad6193cce901356e4c14a830497fc01c91f38c94a871b203ffe657b29c9a98cd16787e3a6a8b45169dd0b471cb36d26d645478a177c958779a + languageName: node + linkType: hard + +"@rollup/plugin-inject@npm:^5.0.5": + version: 5.0.5 + resolution: "@rollup/plugin-inject@npm:5.0.5" + dependencies: + "@rollup/pluginutils": ^5.0.1 + estree-walker: ^2.0.2 + magic-string: ^0.30.3 + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + checksum: 22cb772fd6f7178308b2ece95cdde5f8615f6257197832166294552a7e4c0d3976dc996cbfa6470af3151d8b86c00091aa93da5f4db6ec563f11b6db29fd1b63 + languageName: node + linkType: hard + +"@rollup/plugin-json@npm:^6.1.0": + version: 6.1.0 + resolution: "@rollup/plugin-json@npm:6.1.0" + dependencies: + "@rollup/pluginutils": ^5.1.0 + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + checksum: cc018d20c80242a2b8b44fae61a968049cf31bb8406218187cc7cda35747616594e79452dd65722e7da6dd825b392e90d4599d43cd4461a02fefa2865945164e + languageName: node + linkType: hard + +"@rollup/plugin-node-resolve@npm:^15.2.3": + version: 15.2.3 + resolution: "@rollup/plugin-node-resolve@npm:15.2.3" + dependencies: + "@rollup/pluginutils": ^5.0.1 + "@types/resolve": 1.20.2 + deepmerge: ^4.2.2 + is-builtin-module: ^3.2.1 + is-module: ^1.0.0 + resolve: ^1.22.1 + peerDependencies: + rollup: ^2.78.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + checksum: 730f32c2f8fdddff07cf0fca86a5dac7c475605fb96930197a868c066e62eb6388c557545e4f7d99b7a283411754c9fbf98944ab086b6074e04fc1292e234aa8 + languageName: node + linkType: hard + +"@rollup/plugin-replace@npm:^5.0.7": + version: 5.0.7 + resolution: "@rollup/plugin-replace@npm:5.0.7" + dependencies: + "@rollup/pluginutils": ^5.0.1 + magic-string: ^0.30.3 + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + checksum: 67985e3f4056b92a5f6847b9ddf5b8e9aaecefa0e20b96751dcd63c3ca1f907dadad2940f270867dab2e24bc27da6b0e82f0ce6bb20309aa3465869a9d2e3f13 + languageName: node + linkType: hard + +"@rollup/pluginutils@npm:^4.0.0": + version: 4.2.1 + resolution: "@rollup/pluginutils@npm:4.2.1" + dependencies: + estree-walker: ^2.0.1 picomatch: ^2.2.2 checksum: 6bc41f22b1a0f1efec3043899e4d3b6b1497b3dea4d94292d8f83b4cf07a1073ecbaedd562a22d11913ff7659f459677b01b09e9598a98936e746780ecc93a12 languageName: node linkType: hard +"@rollup/pluginutils@npm:^5.0.1, @rollup/pluginutils@npm:^5.1.0": + version: 5.1.0 + resolution: "@rollup/pluginutils@npm:5.1.0" + dependencies: + "@types/estree": ^1.0.0 + estree-walker: ^2.0.2 + picomatch: ^2.3.1 + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + checksum: 3cc5a6d91452a6eabbfd1ae79b4dd1f1e809d2eecda6e175deb784e75b0911f47e9ecce73f8dd315d6a8b3f362582c91d3c0f66908b6ced69345b3cbe28f8ce8 + languageName: node + linkType: hard + +"@rollup/rollup-android-arm-eabi@npm:4.21.3": + version: 4.21.3 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.21.3" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@rollup/rollup-android-arm64@npm:4.21.3": + version: 4.21.3 + resolution: "@rollup/rollup-android-arm64@npm:4.21.3" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-darwin-arm64@npm:4.21.3": + version: 4.21.3 + resolution: "@rollup/rollup-darwin-arm64@npm:4.21.3" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-darwin-x64@npm:4.21.3": + version: 4.21.3 + resolution: "@rollup/rollup-darwin-x64@npm:4.21.3" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm-gnueabihf@npm:4.21.3": + version: 4.21.3 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.21.3" + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm-musleabihf@npm:4.21.3": + version: 4.21.3 + resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.21.3" + conditions: os=linux & cpu=arm & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm64-gnu@npm:4.21.3": + version: 4.21.3 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.21.3" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm64-musl@npm:4.21.3": + version: 4.21.3 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.21.3" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-powerpc64le-gnu@npm:4.21.3": + version: 4.21.3 + resolution: "@rollup/rollup-linux-powerpc64le-gnu@npm:4.21.3" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-riscv64-gnu@npm:4.21.3": + version: 4.21.3 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.21.3" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-s390x-gnu@npm:4.21.3": + version: 4.21.3 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.21.3" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-x64-gnu@npm:4.21.3": + version: 4.21.3 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.21.3" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-x64-musl@npm:4.21.3": + version: 4.21.3 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.21.3" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-win32-arm64-msvc@npm:4.21.3": + version: 4.21.3 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.21.3" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-win32-ia32-msvc@npm:4.21.3": + version: 4.21.3 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.21.3" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@rollup/rollup-win32-x64-msvc@npm:4.21.3": + version: 4.21.3 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.21.3" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@sindresorhus/merge-streams@npm:^2.1.0": version: 2.3.0 resolution: "@sindresorhus/merge-streams@npm:2.3.0" @@ -335,6 +1582,22 @@ __metadata: languageName: node linkType: hard +"@tybys/wasm-util@npm:^0.9.0": + version: 0.9.0 + resolution: "@tybys/wasm-util@npm:0.9.0" + dependencies: + tslib: ^2.4.0 + checksum: 8d44c64e64e39c746e45b5dff7b534716f20e1f6e8fc206f8e4c8ac454ec0eb35b65646e446dd80745bc898db37a4eca549a936766d447c2158c9c43d44e7708 + languageName: node + linkType: hard + +"@types/estree@npm:*, @types/estree@npm:1.0.5, @types/estree@npm:^1.0.0": + version: 1.0.5 + resolution: "@types/estree@npm:1.0.5" + checksum: dd8b5bed28e6213b7acd0fb665a84e693554d850b0df423ac8076cc3ad5823a6bc26b0251d080bdc545af83179ede51dd3f6fa78cad2c46ed1f29624ddf3e41a + languageName: node + linkType: hard + "@types/istanbul-lib-coverage@npm:^2.0.1": version: 2.0.4 resolution: "@types/istanbul-lib-coverage@npm:2.0.4" @@ -342,6 +1605,15 @@ __metadata: languageName: node linkType: hard +"@types/mute-stream@npm:^0.0.4": + version: 0.0.4 + resolution: "@types/mute-stream@npm:0.0.4" + dependencies: + "@types/node": "*" + checksum: af8d83ad7b68ea05d9357985daf81b6c9b73af4feacb2f5c2693c7fd3e13e5135ef1bd083ce8d5bdc8e97acd28563b61bb32dec4e4508a8067fcd31b8a098632 + languageName: node + linkType: hard + "@types/node@npm:*": version: 20.6.2 resolution: "@types/node@npm:20.6.2" @@ -358,6 +1630,15 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:^22.5.2": + version: 22.5.4 + resolution: "@types/node@npm:22.5.4" + dependencies: + undici-types: ~6.19.2 + checksum: 77ac225c38c428200036780036da0bc6764e2721cfa8f528c7e7da7cfefe01a32a5791e28a54efbeedbc977949058d7db902b2e00139298225d4686cee4ae6db + languageName: node + linkType: hard + "@types/prompts@npm:^2.4.4": version: 2.4.4 resolution: "@types/prompts@npm:2.4.4" @@ -368,6 +1649,20 @@ __metadata: languageName: node linkType: hard +"@types/resolve@npm:1.20.2": + version: 1.20.2 + resolution: "@types/resolve@npm:1.20.2" + checksum: 61c2cad2499ffc8eab36e3b773945d337d848d3ac6b7b0a87c805ba814bc838ef2f262fc0f109bfd8d2e0898ff8bd80ad1025f9ff64f1f71d3d4294c9f14e5f6 + languageName: node + linkType: hard + +"@types/wrap-ansi@npm:^3.0.0": + version: 3.0.0 + resolution: "@types/wrap-ansi@npm:3.0.0" + checksum: 492f0610093b5802f45ca292777679bb9b381f1f32ae939956dd9e00bf81dba7cc99979687620a2817d9a7d8b59928207698166c47a0861c6a2e5c30d4aaf1e9 + languageName: node + linkType: hard + "@vercel/nft@npm:^0.26.2": version: 0.26.5 resolution: "@vercel/nft@npm:0.26.5" @@ -406,11 +1701,12 @@ __metadata: version: 0.0.0-use.local resolution: "@y-octo/node@workspace:y-octo-node" dependencies: - "@napi-rs/cli": ^2.18.4 + "@napi-rs/cli": ^3.0.0-alpha.62 "@types/node": ^20.16.4 "@types/prompts": ^2.4.4 ava: ^6.1.3 c8: ^10.1.2 + pkgroll: ^2.5.0 prompts: ^2.4.2 tsx: ^4.16.2 typescript: ^5.1.6 @@ -487,6 +1783,15 @@ __metadata: languageName: node linkType: hard +"ansi-escapes@npm:^4.3.2": + version: 4.3.2 + resolution: "ansi-escapes@npm:4.3.2" + dependencies: + type-fest: ^0.21.3 + checksum: 93111c42189c0a6bed9cdb4d7f2829548e943827ee8479c74d6e0b22ee127b2a21d3f8b5ca57723b8ef78ce011fbfc2784350eb2bde3ccfccf2f575fa8489815 + languageName: node + linkType: hard + "ansi-escapes@npm:^5.0.0": version: 5.0.0 resolution: "ansi-escapes@npm:5.0.0" @@ -561,6 +1866,13 @@ __metadata: languageName: node linkType: hard +"argparse@npm:^2.0.1": + version: 2.0.1 + resolution: "argparse@npm:2.0.1" + checksum: 83644b56493e89a254bae05702abf3a1101b4fa4d0ca31df1c9985275a5a5bd47b3c27b7fa0b71098d41114d8ca000e6ed90cad764b306f8a503665e4d517ced + languageName: node + linkType: hard + "array-find-index@npm:^1.0.1": version: 1.0.2 resolution: "array-find-index@npm:1.0.2" @@ -651,6 +1963,13 @@ __metadata: languageName: node linkType: hard +"before-after-hook@npm:^3.0.2": + version: 3.0.2 + resolution: "before-after-hook@npm:3.0.2" + checksum: 5f76a9d31909f7f1f7125b7e017ff018799308f5c1fc5a5bfeba9986149da77e6a5cdde0d151671cf374a7fa6452533237bb1de62dfd6c235c20e7c61cc9569d + languageName: node + linkType: hard + "bindings@npm:^1.4.0": version: 1.5.0 resolution: "bindings@npm:1.5.0" @@ -704,6 +2023,13 @@ __metadata: languageName: node linkType: hard +"builtin-modules@npm:^3.3.0": + version: 3.3.0 + resolution: "builtin-modules@npm:3.3.0" + checksum: db021755d7ed8be048f25668fe2117620861ef6703ea2c65ed2779c9e3636d5c3b82325bd912244293959ff3ae303afa3471f6a15bf5060c103e4cc3a839749d + languageName: node + linkType: hard + "c8@npm:^10.1.2": version: 10.1.2 resolution: "c8@npm:10.1.2" @@ -784,6 +2110,13 @@ __metadata: languageName: node linkType: hard +"chardet@npm:^0.7.0": + version: 0.7.0 + resolution: "chardet@npm:0.7.0" + checksum: 6fd5da1f5d18ff5712c1e0aed41da200d7c51c28f11b36ee3c7b483f3696dabc08927fc6b227735eb8f0e1215c9a8abd8154637f3eff8cada5959df7f58b024d + languageName: node + linkType: hard + "chownr@npm:^2.0.0": version: 2.0.0 resolution: "chownr@npm:2.0.0" @@ -828,6 +2161,13 @@ __metadata: languageName: node linkType: hard +"cli-spinners@npm:^2.9.2": + version: 2.9.2 + resolution: "cli-spinners@npm:2.9.2" + checksum: 1bd588289b28432e4676cb5d40505cfe3e53f2e4e10fbe05c8a710a154d6fe0ce7836844b00d6858f740f2ffe67cdc36e0fce9c7b6a8430e80e6388d5aa4956c + languageName: node + linkType: hard + "cli-truncate@npm:^3.1.0": version: 3.1.0 resolution: "cli-truncate@npm:3.1.0" @@ -848,6 +2188,24 @@ __metadata: languageName: node linkType: hard +"cli-width@npm:^4.1.0": + version: 4.1.0 + resolution: "cli-width@npm:4.1.0" + checksum: 0a79cff2dbf89ef530bcd54c713703ba94461457b11e5634bd024c78796ed21401e32349c004995954e06f442d82609287e7aabf6a5f02c919a1cf3b9b6854ff + languageName: node + linkType: hard + +"clipanion@npm:^3.2.1": + version: 3.2.1 + resolution: "clipanion@npm:3.2.1" + dependencies: + typanion: ^3.8.0 + peerDependencies: + typanion: "*" + checksum: 448efd122ead3c802e61ba7a2002e2080c8cce01ce8a0a789d9b9e4f8fe70fd887dcf163ef8c778f5364a9e6f4b498b9f1853f709d7ed4291713e78bcfb88ee8 + languageName: node + linkType: hard + "cliui@npm:^8.0.1": version: 8.0.1 resolution: "cliui@npm:8.0.1" @@ -930,6 +2288,13 @@ __metadata: languageName: node linkType: hard +"commondir@npm:^1.0.1": + version: 1.0.1 + resolution: "commondir@npm:1.0.1" + checksum: 59715f2fc456a73f68826285718503340b9f0dd89bfffc42749906c5cf3d4277ef11ef1cca0350d0e79204f00f1f6d83851ececc9095dc88512a697ac0b9bdcb + languageName: node + linkType: hard + "concat-map@npm:0.0.1": version: 0.0.1 resolution: "concat-map@npm:0.0.1" @@ -1040,6 +2405,13 @@ __metadata: languageName: node linkType: hard +"deepmerge@npm:^4.2.2": + version: 4.3.1 + resolution: "deepmerge@npm:4.3.1" + checksum: 2024c6a980a1b7128084170c4cf56b0fd58a63f2da1660dcfe977415f27b17dbe5888668b59d0b063753f3220719d5e400b7f113609489c90160bb9a5518d052 + languageName: node + linkType: hard + "delegates@npm:^1.0.0": version: 1.0.0 resolution: "delegates@npm:1.0.0" @@ -1068,6 +2440,18 @@ __metadata: languageName: node linkType: hard +"emnapi@npm:^1.2.0": + version: 1.2.0 + resolution: "emnapi@npm:1.2.0" + peerDependencies: + node-addon-api: ">= 6.1.0" + peerDependenciesMeta: + node-addon-api: + optional: true + checksum: ea9df8c47dbf1d9f2b9d92b6e7cb334267bfd27b835403cbc47627fe5a9beddf00fed343ccbe2940a37e634813dfdba80016cba1f518fa25a7e3e482d4a98753 + languageName: node + linkType: hard + "emoji-regex@npm:^10.3.0": version: 10.3.0 resolution: "emoji-regex@npm:10.3.0" @@ -1121,6 +2505,89 @@ __metadata: languageName: node linkType: hard +"esbuild@npm:^0.23.0": + version: 0.23.1 + resolution: "esbuild@npm:0.23.1" + dependencies: + "@esbuild/aix-ppc64": 0.23.1 + "@esbuild/android-arm": 0.23.1 + "@esbuild/android-arm64": 0.23.1 + "@esbuild/android-x64": 0.23.1 + "@esbuild/darwin-arm64": 0.23.1 + "@esbuild/darwin-x64": 0.23.1 + "@esbuild/freebsd-arm64": 0.23.1 + "@esbuild/freebsd-x64": 0.23.1 + "@esbuild/linux-arm": 0.23.1 + "@esbuild/linux-arm64": 0.23.1 + "@esbuild/linux-ia32": 0.23.1 + "@esbuild/linux-loong64": 0.23.1 + "@esbuild/linux-mips64el": 0.23.1 + "@esbuild/linux-ppc64": 0.23.1 + "@esbuild/linux-riscv64": 0.23.1 + "@esbuild/linux-s390x": 0.23.1 + "@esbuild/linux-x64": 0.23.1 + "@esbuild/netbsd-x64": 0.23.1 + "@esbuild/openbsd-arm64": 0.23.1 + "@esbuild/openbsd-x64": 0.23.1 + "@esbuild/sunos-x64": 0.23.1 + "@esbuild/win32-arm64": 0.23.1 + "@esbuild/win32-ia32": 0.23.1 + "@esbuild/win32-x64": 0.23.1 + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-arm64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 0413c3b9257327fb598427688b7186ea335bf1693746fe5713cc93c95854d6388b8ed4ad643fddf5b5ace093f7dcd9038dd58e087bf2da1f04dfb4c5571660af + languageName: node + linkType: hard + "esbuild@npm:~0.21.5": version: 0.21.5 resolution: "esbuild@npm:0.21.5" @@ -1239,7 +2706,7 @@ __metadata: languageName: node linkType: hard -"estree-walker@npm:2.0.2, estree-walker@npm:^2.0.1": +"estree-walker@npm:2.0.2, estree-walker@npm:^2.0.1, estree-walker@npm:^2.0.2": version: 2.0.2 resolution: "estree-walker@npm:2.0.2" checksum: 6151e6f9828abe2259e57f5fd3761335bb0d2ebd76dc1a01048ccee22fabcfef3c0859300f6d83ff0d1927849368775ec5a6d265dde2f6de5a1be1721cd94efc @@ -1284,6 +2751,17 @@ __metadata: languageName: node linkType: hard +"external-editor@npm:^3.1.0": + version: 3.1.0 + resolution: "external-editor@npm:3.1.0" + dependencies: + chardet: ^0.7.0 + iconv-lite: ^0.4.24 + tmp: ^0.0.33 + checksum: 1c2a616a73f1b3435ce04030261bed0e22d4737e14b090bb48e58865da92529c9f2b05b893de650738d55e692d071819b45e1669259b2b354bc3154d27a698c7 + languageName: node + linkType: hard + "fast-diff@npm:^1.2.0": version: 1.3.0 resolution: "fast-diff@npm:1.3.0" @@ -1409,7 +2887,7 @@ __metadata: languageName: node linkType: hard -"fsevents@npm:~2.3.3": +"fsevents@npm:~2.3.2, fsevents@npm:~2.3.3": version: 2.3.3 resolution: "fsevents@npm:2.3.3" dependencies: @@ -1419,7 +2897,7 @@ __metadata: languageName: node linkType: hard -"fsevents@patch:fsevents@~2.3.3#~builtin": +"fsevents@patch:fsevents@~2.3.2#~builtin, fsevents@patch:fsevents@~2.3.3#~builtin": version: 2.3.3 resolution: "fsevents@patch:fsevents@npm%3A2.3.3#~builtin::version=2.3.3&hash=df0bf1" dependencies: @@ -1639,6 +3117,15 @@ __metadata: languageName: node linkType: hard +"iconv-lite@npm:^0.4.24": + version: 0.4.24 + resolution: "iconv-lite@npm:0.4.24" + dependencies: + safer-buffer: ">= 2.1.2 < 3" + checksum: bd9f120f5a5b306f0bc0b9ae1edeb1577161503f5f8252a20f1a9e56ef8775c9959fd01c55f2d3a39d9a8abaf3e30c1abeb1895f367dcbbe0a8fd1c9ca01c4f6 + languageName: node + linkType: hard + "iconv-lite@npm:^0.6.2": version: 0.6.3 resolution: "iconv-lite@npm:0.6.3" @@ -1700,6 +3187,22 @@ __metadata: languageName: node linkType: hard +"inquirer@npm:^10.0.0": + version: 10.2.2 + resolution: "inquirer@npm:10.2.2" + dependencies: + "@inquirer/core": ^9.1.0 + "@inquirer/prompts": ^5.5.0 + "@inquirer/type": ^1.5.3 + "@types/mute-stream": ^0.0.4 + ansi-escapes: ^4.3.2 + mute-stream: ^1.0.0 + run-async: ^3.0.0 + rxjs: ^7.8.1 + checksum: 41e09d818f9545d52698f69f6c832f6a2ea2cf40e7b0e056147aadd8ea4b76d489a2a10e34b0593cd1f43989def3114c086502166c2a0bdab353ea5c43f4173b + languageName: node + linkType: hard + "ip-address@npm:^9.0.5": version: 9.0.5 resolution: "ip-address@npm:9.0.5" @@ -1724,6 +3227,15 @@ __metadata: languageName: node linkType: hard +"is-builtin-module@npm:^3.2.1": + version: 3.2.1 + resolution: "is-builtin-module@npm:3.2.1" + dependencies: + builtin-modules: ^3.3.0 + checksum: e8f0ffc19a98240bda9c7ada84d846486365af88d14616e737d280d378695c8c448a621dcafc8332dbf0fcd0a17b0763b845400709963fa9151ddffece90ae88 + languageName: node + linkType: hard + "is-core-module@npm:@nolyfill/is-core-module@^1": version: 1.0.39 resolution: "@nolyfill/is-core-module@npm:1.0.39" @@ -1768,6 +3280,13 @@ __metadata: languageName: node linkType: hard +"is-module@npm:^1.0.0": + version: 1.0.0 + resolution: "is-module@npm:1.0.0" + checksum: 8cd5390730c7976fb4e8546dd0b38865ee6f7bacfa08dfbb2cc07219606755f0b01709d9361e01f13009bbbd8099fa2927a8ed665118a6105d66e40f1b838c3f + languageName: node + linkType: hard + "is-number@npm:^7.0.0": version: 7.0.0 resolution: "is-number@npm:7.0.0" @@ -1789,6 +3308,15 @@ __metadata: languageName: node linkType: hard +"is-reference@npm:1.2.1": + version: 1.2.1 + resolution: "is-reference@npm:1.2.1" + dependencies: + "@types/estree": "*" + checksum: e7b48149f8abda2c10849ea51965904d6a714193d68942ad74e30522231045acf06cbfae5a4be2702fede5d232e61bf50b3183acdc056e6e3afe07fcf4f4b2bc + languageName: node + linkType: hard + "is-stream@npm:^3.0.0": version: 3.0.0 resolution: "is-stream@npm:3.0.0" @@ -1884,6 +3412,17 @@ __metadata: languageName: node linkType: hard +"js-yaml@npm:^4.1.0": + version: 4.1.0 + resolution: "js-yaml@npm:4.1.0" + dependencies: + argparse: ^2.0.1 + bin: + js-yaml: bin/js-yaml.js + checksum: c7830dfd456c3ef2c6e355cc5a92e6700ceafa1d14bba54497b34a99f0376cecbb3e9ac14d3e5849b426d5a5140709a66237a8c991c675431271c4ce5504151a + languageName: node + linkType: hard + "jsbn@npm:1.1.0": version: 1.1.0 resolution: "jsbn@npm:1.1.0" @@ -1992,6 +3531,13 @@ __metadata: languageName: node linkType: hard +"lodash-es@npm:^4.17.21": + version: 4.17.21 + resolution: "lodash-es@npm:4.17.21" + checksum: 05cbffad6e2adbb331a4e16fbd826e7faee403a1a04873b82b42c0f22090f280839f85b95393f487c1303c8a3d2a010048bf06151a6cbe03eee4d388fb0a12d2 + languageName: node + linkType: hard + "lodash@npm:^4.17.15": version: 4.17.21 resolution: "lodash@npm:4.17.21" @@ -2019,6 +3565,15 @@ __metadata: languageName: node linkType: hard +"magic-string@npm:^0.30.10, magic-string@npm:^0.30.3": + version: 0.30.11 + resolution: "magic-string@npm:0.30.11" + dependencies: + "@jridgewell/sourcemap-codec": ^1.5.0 + checksum: e041649453c9a3f31d2e731fc10e38604d50e20d3585cd48bc7713a6e2e1a3ad3012105929ca15750d59d0a3f1904405e4b95a23b7e69dc256db3c277a73a3ca + languageName: node + linkType: hard + "make-dir@npm:^3.1.0": version: 3.1.0 resolution: "make-dir@npm:3.1.0" @@ -2271,6 +3826,13 @@ __metadata: languageName: node linkType: hard +"mute-stream@npm:^1.0.0": + version: 1.0.0 + resolution: "mute-stream@npm:1.0.0" + checksum: 36fc968b0e9c9c63029d4f9dc63911950a3bdf55c9a87f58d3a266289b67180201cade911e7699f8b2fa596b34c9db43dad37649e3f7fdd13c3bb9edb0017ee7 + languageName: node + linkType: hard + "negotiator@npm:^0.6.3": version: 0.6.3 resolution: "negotiator@npm:0.6.3" @@ -2447,6 +4009,13 @@ __metadata: languageName: node linkType: hard +"os-tmpdir@npm:~1.0.2": + version: 1.0.2 + resolution: "os-tmpdir@npm:1.0.2" + checksum: 5666560f7b9f10182548bf7013883265be33620b1c1b4a4d405c25be2636f970c5488ff3e6c48de75b55d02bde037249fe5dbfbb4c0fb7714953d56aed062e6d + languageName: node + linkType: hard + "p-limit@npm:^3.0.2": version: 3.1.0 resolution: "p-limit@npm:3.1.0" @@ -2622,6 +4191,31 @@ __metadata: languageName: node linkType: hard +"pkgroll@npm:^2.5.0": + version: 2.5.0 + resolution: "pkgroll@npm:2.5.0" + dependencies: + "@rollup/plugin-alias": ^5.1.0 + "@rollup/plugin-commonjs": ^26.0.1 + "@rollup/plugin-inject": ^5.0.5 + "@rollup/plugin-json": ^6.1.0 + "@rollup/plugin-node-resolve": ^15.2.3 + "@rollup/plugin-replace": ^5.0.7 + "@rollup/pluginutils": ^5.1.0 + esbuild: ^0.23.0 + magic-string: ^0.30.10 + rollup: ^4.18.1 + peerDependencies: + typescript: ^4.1 || ^5.0 + peerDependenciesMeta: + typescript: + optional: true + bin: + pkgroll: dist/cli.js + checksum: e5526c8fed5252d168526dc4d0ebcdc60fc6d7fc21c2c76bc7f497ac06cdc2ea3a6547ac0cfca3bf28ea4a478dff708bde1632f215a1256789b0b0adfbc37b48 + languageName: node + linkType: hard + "plur@npm:^5.1.0": version: 5.1.0 resolution: "plur@npm:5.1.0" @@ -2755,6 +4349,19 @@ __metadata: languageName: node linkType: hard +"resolve@npm:^1.22.1": + version: 1.22.8 + resolution: "resolve@npm:1.22.8" + dependencies: + is-core-module: ^2.13.0 + path-parse: ^1.0.7 + supports-preserve-symlinks-flag: ^1.0.0 + bin: + resolve: bin/resolve + checksum: f8a26958aa572c9b064562750b52131a37c29d072478ea32e129063e2da7f83e31f7f11e7087a18225a8561cfe8d2f0df9dbea7c9d331a897571c0a2527dbb4c + languageName: node + linkType: hard + "resolve@patch:resolve@^1.10.0#~builtin": version: 1.22.4 resolution: "resolve@patch:resolve@npm%3A1.22.4#~builtin::version=1.22.4&hash=c3c19d" @@ -2768,6 +4375,19 @@ __metadata: languageName: node linkType: hard +"resolve@patch:resolve@^1.22.1#~builtin": + version: 1.22.8 + resolution: "resolve@patch:resolve@npm%3A1.22.8#~builtin::version=1.22.8&hash=c3c19d" + dependencies: + is-core-module: ^2.13.0 + path-parse: ^1.0.7 + supports-preserve-symlinks-flag: ^1.0.0 + bin: + resolve: bin/resolve + checksum: 5479b7d431cacd5185f8db64bfcb7286ae5e31eb299f4c4f404ad8aa6098b77599563ac4257cb2c37a42f59dfc06a1bec2bcf283bb448f319e37f0feb9a09847 + languageName: node + linkType: hard + "restore-cursor@npm:^4.0.0": version: 4.0.0 resolution: "restore-cursor@npm:4.0.0" @@ -2810,6 +4430,76 @@ __metadata: languageName: node linkType: hard +"rollup@npm:^4.18.1": + version: 4.21.3 + resolution: "rollup@npm:4.21.3" + dependencies: + "@rollup/rollup-android-arm-eabi": 4.21.3 + "@rollup/rollup-android-arm64": 4.21.3 + "@rollup/rollup-darwin-arm64": 4.21.3 + "@rollup/rollup-darwin-x64": 4.21.3 + "@rollup/rollup-linux-arm-gnueabihf": 4.21.3 + "@rollup/rollup-linux-arm-musleabihf": 4.21.3 + "@rollup/rollup-linux-arm64-gnu": 4.21.3 + "@rollup/rollup-linux-arm64-musl": 4.21.3 + "@rollup/rollup-linux-powerpc64le-gnu": 4.21.3 + "@rollup/rollup-linux-riscv64-gnu": 4.21.3 + "@rollup/rollup-linux-s390x-gnu": 4.21.3 + "@rollup/rollup-linux-x64-gnu": 4.21.3 + "@rollup/rollup-linux-x64-musl": 4.21.3 + "@rollup/rollup-win32-arm64-msvc": 4.21.3 + "@rollup/rollup-win32-ia32-msvc": 4.21.3 + "@rollup/rollup-win32-x64-msvc": 4.21.3 + "@types/estree": 1.0.5 + fsevents: ~2.3.2 + dependenciesMeta: + "@rollup/rollup-android-arm-eabi": + optional: true + "@rollup/rollup-android-arm64": + optional: true + "@rollup/rollup-darwin-arm64": + optional: true + "@rollup/rollup-darwin-x64": + optional: true + "@rollup/rollup-linux-arm-gnueabihf": + optional: true + "@rollup/rollup-linux-arm-musleabihf": + optional: true + "@rollup/rollup-linux-arm64-gnu": + optional: true + "@rollup/rollup-linux-arm64-musl": + optional: true + "@rollup/rollup-linux-powerpc64le-gnu": + optional: true + "@rollup/rollup-linux-riscv64-gnu": + optional: true + "@rollup/rollup-linux-s390x-gnu": + optional: true + "@rollup/rollup-linux-x64-gnu": + optional: true + "@rollup/rollup-linux-x64-musl": + optional: true + "@rollup/rollup-win32-arm64-msvc": + optional: true + "@rollup/rollup-win32-ia32-msvc": + optional: true + "@rollup/rollup-win32-x64-msvc": + optional: true + fsevents: + optional: true + bin: + rollup: dist/bin/rollup + checksum: 19689840d25ced3924124b012d7e0048f2b0844a0765a5dde0804ae9961af1103657c2ec3e90f7a19876ebe40b3fa2c33f53fca071d46639c57bd327b82aba22 + languageName: node + linkType: hard + +"run-async@npm:^3.0.0": + version: 3.0.0 + resolution: "run-async@npm:3.0.0" + checksum: 280c03d5a88603f48103fc6fd69f07fb0c392a1e0d319c34ec96a2516030e07ba06f79231a563c78698b882649c2fc1fda601bc84705f57d50efcd1fa506cfc0 + languageName: node + linkType: hard + "run-parallel@npm:^1.1.9": version: 1.2.0 resolution: "run-parallel@npm:1.2.0" @@ -2819,6 +4509,15 @@ __metadata: languageName: node linkType: hard +"rxjs@npm:^7.8.1": + version: 7.8.1 + resolution: "rxjs@npm:7.8.1" + dependencies: + tslib: ^2.1.0 + checksum: de4b53db1063e618ec2eca0f7965d9137cabe98cf6be9272efe6c86b47c17b987383df8574861bcced18ebd590764125a901d5506082be84a8b8e364bf05f119 + languageName: node + linkType: hard + "safe-buffer@npm:~5.2.0": version: 5.2.1 resolution: "safe-buffer@npm:5.2.1" @@ -2826,7 +4525,7 @@ __metadata: languageName: node linkType: hard -"safer-buffer@npm:>= 2.1.2 < 3.0.0": +"safer-buffer@npm:>= 2.1.2 < 3, safer-buffer@npm:>= 2.1.2 < 3.0.0": version: 2.1.2 resolution: "safer-buffer@npm:2.1.2" checksum: cab8f25ae6f1434abee8d80023d7e72b598cf1327164ddab31003c51215526801e40b66c5e65d658a0af1e9d6478cadcb4c745f4bd6751f97d8644786c0978b0 @@ -2860,6 +4559,15 @@ __metadata: languageName: node linkType: hard +"semver@npm:^7.5.4": + version: 7.6.3 + resolution: "semver@npm:7.6.3" + bin: + semver: bin/semver.js + checksum: 4110ec5d015c9438f322257b1c51fe30276e5f766a3f64c09edd1d7ea7118ecbc3f379f3b69032bacf13116dc7abc4ad8ce0d7e2bd642e26b0d271b56b61a7d8 + languageName: node + linkType: hard + "serialize-error@npm:^7.0.1": version: 7.0.1 resolution: "serialize-error@npm:7.0.1" @@ -2922,7 +4630,7 @@ __metadata: languageName: node linkType: hard -"signal-exit@npm:^4.0.1": +"signal-exit@npm:^4.0.1, signal-exit@npm:^4.1.0": version: 4.1.0 resolution: "signal-exit@npm:4.1.0" checksum: 64c757b498cb8629ffa5f75485340594d2f8189e9b08700e69199069c8e3070fb3e255f7ab873c05dc0b3cec412aea7402e10a5990cb6a050bd33ba062a6c549 @@ -2936,6 +4644,13 @@ __metadata: languageName: node linkType: hard +"slash@npm:^4.0.0": + version: 4.0.0 + resolution: "slash@npm:4.0.0" + checksum: da8e4af73712253acd21b7853b7e0dbba776b786e82b010a5bfc8b5051a1db38ed8aba8e1e8f400dd2c9f373be91eb1c42b66e91abb407ff42b10feece5e1d2d + languageName: node + linkType: hard + "slash@npm:^5.1.0": version: 5.1.0 resolution: "slash@npm:5.1.0" @@ -3213,6 +4928,15 @@ __metadata: languageName: node linkType: hard +"tmp@npm:^0.0.33": + version: 0.0.33 + resolution: "tmp@npm:0.0.33" + dependencies: + os-tmpdir: ~1.0.2 + checksum: 902d7aceb74453ea02abbf58c203f4a8fc1cead89b60b31e354f74ed5b3fb09ea817f94fb310f884a5d16987dd9fa5a735412a7c2dd088dd3d415aa819ae3a28 + languageName: node + linkType: hard + "to-regex-range@npm:^5.0.1": version: 5.0.1 resolution: "to-regex-range@npm:5.0.1" @@ -3222,6 +4946,13 @@ __metadata: languageName: node linkType: hard +"toml@npm:^3.0.0": + version: 3.0.0 + resolution: "toml@npm:3.0.0" + checksum: 5d7f1d8413ad7780e9bdecce8ea4c3f5130dd53b0a4f2e90b93340979a137739879d7b9ce2ce05c938b8cc828897fe9e95085197342a1377dd8850bf5125f15f + languageName: node + linkType: hard + "tr46@npm:~0.0.3": version: 0.0.3 resolution: "tr46@npm:0.0.3" @@ -3229,6 +4960,13 @@ __metadata: languageName: node linkType: hard +"tslib@npm:^2.1.0, tslib@npm:^2.4.0": + version: 2.7.0 + resolution: "tslib@npm:2.7.0" + checksum: 1606d5c89f88d466889def78653f3aab0f88692e80bb2066d090ca6112ae250ec1cfa9dbfaab0d17b60da15a4186e8ec4d893801c67896b277c17374e36e1d28 + languageName: node + linkType: hard + "tsx@npm:^4.16.2": version: 4.16.2 resolution: "tsx@npm:4.16.2" @@ -3245,6 +4983,13 @@ __metadata: languageName: node linkType: hard +"typanion@npm:^3.14.0, typanion@npm:^3.8.0": + version: 3.14.0 + resolution: "typanion@npm:3.14.0" + checksum: fc0590d02c13c659eb1689e8adf7777e6c00dc911377e44cd36fe1b1271cfaca71547149f12cdc275058c0de5562a14e5273adbae66d47e6e0320e36007f5912 + languageName: node + linkType: hard + "type-fest@npm:^0.13.1": version: 0.13.1 resolution: "type-fest@npm:0.13.1" @@ -3252,6 +4997,13 @@ __metadata: languageName: node linkType: hard +"type-fest@npm:^0.21.3": + version: 0.21.3 + resolution: "type-fest@npm:0.21.3" + checksum: e6b32a3b3877f04339bae01c193b273c62ba7bfc9e325b8703c4ee1b32dc8fe4ef5dfa54bf78265e069f7667d058e360ae0f37be5af9f153b22382cd55a9afe0 + languageName: node + linkType: hard + "type-fest@npm:^1.0.2": version: 1.4.0 resolution: "type-fest@npm:1.4.0" @@ -3311,6 +5063,13 @@ __metadata: languageName: node linkType: hard +"universal-user-agent@npm:^7.0.0, universal-user-agent@npm:^7.0.2": + version: 7.0.2 + resolution: "universal-user-agent@npm:7.0.2" + checksum: 3f02cb6de0bb9fbaf379566bd0320d8e46af6e4358a2e88fce7e70687ed7b48b37f479d728bb22f4204a518e363f3038ac4841c033af1ee2253f6428a6c67e53 + languageName: node + linkType: hard + "util-deprecate@npm:^1.0.1": version: 1.0.2 resolution: "util-deprecate@npm:1.0.2" @@ -3339,6 +5098,15 @@ __metadata: languageName: node linkType: hard +"wasm-sjlj@npm:^1.0.5": + version: 1.0.5 + resolution: "wasm-sjlj@npm:1.0.5" + dependencies: + node-gyp: latest + checksum: 3520b8fc201df62f1a90c4f636c85467bef81799deffa095245dd02594cb1f80840e89d9ce57c05e47b0b38b0ff27318b68a146871e310d00505c9d822f6f5fa + languageName: node + linkType: hard + "webidl-conversions@npm:^3.0.0": version: 3.0.1 resolution: "webidl-conversions@npm:3.0.1" @@ -3416,6 +5184,17 @@ __metadata: languageName: node linkType: hard +"wrap-ansi@npm:^6.2.0": + version: 6.2.0 + resolution: "wrap-ansi@npm:6.2.0" + dependencies: + ansi-styles: ^4.0.0 + string-width: ^4.1.0 + strip-ansi: ^6.0.0 + checksum: 6cd96a410161ff617b63581a08376f0cb9162375adeb7956e10c8cd397821f7eb2a6de24eb22a0b28401300bf228c86e50617cd568209b5f6775b93c97d2fe3a + languageName: node + linkType: hard + "wrap-ansi@npm:^8.0.1, wrap-ansi@npm:^8.1.0": version: 8.1.0 resolution: "wrap-ansi@npm:8.1.0" @@ -3502,3 +5281,10 @@ __metadata: checksum: f77b3d8d00310def622123df93d4ee654fc6a0096182af8bd60679ddcdfb3474c56c6c7190817c84a2785648cdee9d721c0154eb45698c62176c322fb46fc700 languageName: node linkType: hard + +"yoctocolors-cjs@npm:^2.1.2": + version: 2.1.2 + resolution: "yoctocolors-cjs@npm:2.1.2" + checksum: 1c474d4b30a8c130e679279c5c2c33a0d48eba9684ffa0252cc64846c121fb56c3f25457fef902edbe1e2d7a7872130073a9fc8e795299d75e13fa3f5f548f1b + languageName: node + linkType: hard From 1b561c8bf718c2fdbdf307d7890d2105ece6ccef Mon Sep 17 00:00:00 2001 From: DarkSky Date: Tue, 10 Sep 2024 15:55:50 +0800 Subject: [PATCH 66/75] feat: bundle ts after build binding --- y-octo-node/package.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/y-octo-node/package.json b/y-octo-node/package.json index 77e8614..ef65e95 100644 --- a/y-octo-node/package.json +++ b/y-octo-node/package.json @@ -38,9 +38,11 @@ }, "scripts": { "artifacts": "napi artifacts", - "build": "napi build --platform --release --no-const-enum --dts ../src/yocto.d.ts --js ../src/yocto.js -o ./dist", + "build": "yarn build:binding && yarn build:js", + "build:debug": "yarn build:binding:debug && yarn build:js", "build:js": "pkgroll", - "build:debug": "napi build --platform --no-const-enum --dts ../src/yocto.d.ts --js ../src/yocto.js -o ./dist", + "build:binding": "napi build --platform --release --no-const-enum --dts ../src/yocto.d.ts --js ../src/yocto.js -o ./dist", + "build:binding:debug": "napi build --platform --no-const-enum --dts ../src/yocto.d.ts --js ../src/yocto.js -o ./dist", "universal": "napi universal", "test": "ava --concurrency 1 --serial --timeout 5s", "test:watch": "yarn exec tsx ./scripts/run-test.mts all --watch", From 4f6ee2ecc15228fc7ab3da1f461655a3fde4f9ec Mon Sep 17 00:00:00 2001 From: DarkSky Date: Tue, 10 Sep 2024 16:19:01 +0800 Subject: [PATCH 67/75] chore: fix build warning --- tsconfig.json | 2 +- y-octo-node/package.json | 4 ++-- y-octo-node/src/text.rs | 4 ++-- y-octo-node/src/utils/ytype.rs | 1 + 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/tsconfig.json b/tsconfig.json index c014b81..8350fba 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -48,7 +48,7 @@ // Projects "composite": true, - "incremental": true, + // "incremental": true, // Completeness "skipLibCheck": true, // skip all type checks for .d.ts files diff --git a/y-octo-node/package.json b/y-octo-node/package.json index ef65e95..2f66fc9 100644 --- a/y-octo-node/package.json +++ b/y-octo-node/package.json @@ -6,8 +6,8 @@ "types": "./dist/index.d.ts", "exports": "./dist/index.js", "napi": { - "name": "y-octo", - "triples": { + "binaryName": "y-octo", + "target": { "additional": [ "aarch64-apple-darwin", "aarch64-pc-windows-msvc", diff --git a/y-octo-node/src/text.rs b/y-octo-node/src/text.rs index 8a35a79..a6634e6 100644 --- a/y-octo-node/src/text.rs +++ b/y-octo-node/src/text.rs @@ -41,12 +41,12 @@ impl YText { } #[napi(ts_args_type = "delta: any[]")] - pub fn apply_delta(&mut self, env: Env, _delta: JsArray) -> Result<()> { + pub fn apply_delta(&mut self, _env: Env, _delta: JsArray) -> Result<()> { unimplemented!() } #[napi(ts_return_type = "any[]")] - pub fn to_delta(&self, env: Env) -> Result { + pub fn to_delta(&self, _env: Env) -> Result { unimplemented!() } diff --git a/y-octo-node/src/utils/ytype.rs b/y-octo-node/src/utils/ytype.rs index 2521e21..396767e 100644 --- a/y-octo-node/src/utils/ytype.rs +++ b/y-octo-node/src/utils/ytype.rs @@ -10,6 +10,7 @@ pub type MixedYType = Either4; macro_rules! impl_into_mixed_y_type { ($type:ty, $enum:ident) => { + #[allow(clippy::from_over_into)] impl Into for $type { fn into(self) -> MixedYType { MixedYType::$enum(self.clone()) From f4354f4a73fa76322d7e82706347f3ee1b0f682d Mon Sep 17 00:00:00 2001 From: DarkSky Date: Tue, 10 Sep 2024 17:16:18 +0800 Subject: [PATCH 68/75] chore: split project --- Cargo.toml | 2 +- y-octo-node/{ => native}/Cargo.toml | 0 y-octo-node/{ => native}/build.rs | 0 y-octo-node/{ => native}/src/array.rs | 0 y-octo-node/{ => native}/src/awareness.rs | 0 y-octo-node/{ => native}/src/doc.rs | 0 y-octo-node/{ => native}/src/function.rs | 0 y-octo-node/{ => native}/src/lib.rs | 0 y-octo-node/{ => native}/src/map.rs | 0 y-octo-node/{ => native}/src/protocol.rs | 0 y-octo-node/{ => native}/src/text.rs | 0 y-octo-node/{ => native}/src/types.rs | 0 y-octo-node/{ => native}/src/utils/from_js.rs | 0 y-octo-node/{ => native}/src/utils/mod.rs | 0 y-octo-node/{ => native}/src/utils/to_js.rs | 0 y-octo-node/{ => native}/src/utils/ytype.rs | 0 y-octo-node/package.json | 4 +- y-octo-node/scripts/run-test.mts | 86 ------------------- 18 files changed, 3 insertions(+), 89 deletions(-) rename y-octo-node/{ => native}/Cargo.toml (100%) rename y-octo-node/{ => native}/build.rs (100%) rename y-octo-node/{ => native}/src/array.rs (100%) rename y-octo-node/{ => native}/src/awareness.rs (100%) rename y-octo-node/{ => native}/src/doc.rs (100%) rename y-octo-node/{ => native}/src/function.rs (100%) rename y-octo-node/{ => native}/src/lib.rs (100%) rename y-octo-node/{ => native}/src/map.rs (100%) rename y-octo-node/{ => native}/src/protocol.rs (100%) rename y-octo-node/{ => native}/src/text.rs (100%) rename y-octo-node/{ => native}/src/types.rs (100%) rename y-octo-node/{ => native}/src/utils/from_js.rs (100%) rename y-octo-node/{ => native}/src/utils/mod.rs (100%) rename y-octo-node/{ => native}/src/utils/to_js.rs (100%) rename y-octo-node/{ => native}/src/utils/ytype.rs (100%) delete mode 100755 y-octo-node/scripts/run-test.mts diff --git a/Cargo.toml b/Cargo.toml index e9ad093..c1dd66e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ members = [ "y-octo", - "y-octo-node", + "y-octo-node/native", "y-octo-utils", "y-octo-utils/yrs-is-unsafe", ] diff --git a/y-octo-node/Cargo.toml b/y-octo-node/native/Cargo.toml similarity index 100% rename from y-octo-node/Cargo.toml rename to y-octo-node/native/Cargo.toml diff --git a/y-octo-node/build.rs b/y-octo-node/native/build.rs similarity index 100% rename from y-octo-node/build.rs rename to y-octo-node/native/build.rs diff --git a/y-octo-node/src/array.rs b/y-octo-node/native/src/array.rs similarity index 100% rename from y-octo-node/src/array.rs rename to y-octo-node/native/src/array.rs diff --git a/y-octo-node/src/awareness.rs b/y-octo-node/native/src/awareness.rs similarity index 100% rename from y-octo-node/src/awareness.rs rename to y-octo-node/native/src/awareness.rs diff --git a/y-octo-node/src/doc.rs b/y-octo-node/native/src/doc.rs similarity index 100% rename from y-octo-node/src/doc.rs rename to y-octo-node/native/src/doc.rs diff --git a/y-octo-node/src/function.rs b/y-octo-node/native/src/function.rs similarity index 100% rename from y-octo-node/src/function.rs rename to y-octo-node/native/src/function.rs diff --git a/y-octo-node/src/lib.rs b/y-octo-node/native/src/lib.rs similarity index 100% rename from y-octo-node/src/lib.rs rename to y-octo-node/native/src/lib.rs diff --git a/y-octo-node/src/map.rs b/y-octo-node/native/src/map.rs similarity index 100% rename from y-octo-node/src/map.rs rename to y-octo-node/native/src/map.rs diff --git a/y-octo-node/src/protocol.rs b/y-octo-node/native/src/protocol.rs similarity index 100% rename from y-octo-node/src/protocol.rs rename to y-octo-node/native/src/protocol.rs diff --git a/y-octo-node/src/text.rs b/y-octo-node/native/src/text.rs similarity index 100% rename from y-octo-node/src/text.rs rename to y-octo-node/native/src/text.rs diff --git a/y-octo-node/src/types.rs b/y-octo-node/native/src/types.rs similarity index 100% rename from y-octo-node/src/types.rs rename to y-octo-node/native/src/types.rs diff --git a/y-octo-node/src/utils/from_js.rs b/y-octo-node/native/src/utils/from_js.rs similarity index 100% rename from y-octo-node/src/utils/from_js.rs rename to y-octo-node/native/src/utils/from_js.rs diff --git a/y-octo-node/src/utils/mod.rs b/y-octo-node/native/src/utils/mod.rs similarity index 100% rename from y-octo-node/src/utils/mod.rs rename to y-octo-node/native/src/utils/mod.rs diff --git a/y-octo-node/src/utils/to_js.rs b/y-octo-node/native/src/utils/to_js.rs similarity index 100% rename from y-octo-node/src/utils/to_js.rs rename to y-octo-node/native/src/utils/to_js.rs diff --git a/y-octo-node/src/utils/ytype.rs b/y-octo-node/native/src/utils/ytype.rs similarity index 100% rename from y-octo-node/src/utils/ytype.rs rename to y-octo-node/native/src/utils/ytype.rs diff --git a/y-octo-node/package.json b/y-octo-node/package.json index 2f66fc9..7494b01 100644 --- a/y-octo-node/package.json +++ b/y-octo-node/package.json @@ -41,8 +41,8 @@ "build": "yarn build:binding && yarn build:js", "build:debug": "yarn build:binding:debug && yarn build:js", "build:js": "pkgroll", - "build:binding": "napi build --platform --release --no-const-enum --dts ../src/yocto.d.ts --js ../src/yocto.js -o ./dist", - "build:binding:debug": "napi build --platform --no-const-enum --dts ../src/yocto.d.ts --js ../src/yocto.js -o ./dist", + "build:binding": "yarn build:binding:debug --release", + "build:binding:debug": "napi build --platform --no-const-enum --manifest-path native/Cargo.toml --dts ../src/yocto.d.ts --js ../src/yocto.js -o ./dist", "universal": "napi universal", "test": "ava --concurrency 1 --serial --timeout 5s", "test:watch": "yarn exec tsx ./scripts/run-test.mts all --watch", diff --git a/y-octo-node/scripts/run-test.mts b/y-octo-node/scripts/run-test.mts deleted file mode 100755 index 3122356..0000000 --- a/y-octo-node/scripts/run-test.mts +++ /dev/null @@ -1,86 +0,0 @@ -#!/usr/bin/env tsx -import { resolve } from "node:path"; - -import prompts from "prompts"; -import { spawn } from "child_process"; -import { readdir } from "fs/promises"; -import * as process from "process"; -import { fileURLToPath } from "url"; - -import pkg from "../package.json" assert { type: "json" }; -const root = fileURLToPath(new URL("..", import.meta.url)); -const testDir = resolve(root, "tests"); -const files = await readdir(testDir); -const yjsTestDir = resolve(testDir, "yjs"); -const yjsFiles = (await readdir(yjsTestDir)).filter((f) => - f.endsWith(".spec.mts"), -); - -const watchMode = process.argv.includes("--watch"); - -const sharedArgs = [ - ...pkg.sharedConfig.nodeArgs, - "--test", - watchMode ? "--watch" : "", -]; - -const env = { - ...pkg.sharedConfig.env, - PATH: process.env.PATH, - NODE_ENV: "test", - NODE_NO_WARNINGS: "1", -}; - -if (process.argv[2] === "all") { - const cp = spawn( - "node", - [ - ...sharedArgs, - ...files.map((f) => resolve(testDir, f)), - ...yjsFiles.map((f) => resolve(yjsTestDir, f)), - ], - { - cwd: root, - env, - stdio: "inherit", - shell: true, - }, - ); - cp.on("exit", (code) => { - process.exit(code ?? 0); - }); -} else { - const result = await prompts([ - { - type: "select", - name: "file", - message: "Select a file to run", - choices: files.map((file) => ({ - title: file, - value: file, - })), - initial: 1, - }, - ]); - - const target = resolve(testDir, result.file); - - const cp = spawn( - "node", - [ - ...sharedArgs, - "--test-reporter=spec", - "--test-reporter-destination=stdout", - target, - ], - { - cwd: root, - env, - stdio: "inherit", - shell: true, - }, - ); - cp.on("exit", (code) => { - process.exit(code ?? 0); - }); -} From a9ae04238d1ca025efe4049c307feb0fceb62e01 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Wed, 11 Sep 2024 17:28:09 +0800 Subject: [PATCH 69/75] feat: split modules --- y-octo-node/src/array.ts | 140 +++++++++++++ y-octo-node/src/doc.ts | 85 ++++++++ y-octo-node/src/index.ts | 393 +----------------------------------- y-octo-node/src/map.ts | 140 +++++++++++++ y-octo-node/src/protocol.ts | 16 ++ y-octo-node/src/text.ts | 3 + y-octo-node/src/types.ts | 19 ++ 7 files changed, 408 insertions(+), 388 deletions(-) create mode 100644 y-octo-node/src/array.ts create mode 100644 y-octo-node/src/doc.ts create mode 100644 y-octo-node/src/map.ts create mode 100644 y-octo-node/src/protocol.ts create mode 100644 y-octo-node/src/text.ts create mode 100644 y-octo-node/src/types.ts diff --git a/y-octo-node/src/array.ts b/y-octo-node/src/array.ts new file mode 100644 index 0000000..36fccb1 --- /dev/null +++ b/y-octo-node/src/array.ts @@ -0,0 +1,140 @@ +import * as Y from "./yocto"; +import { Doc } from "./doc"; +import { Map } from "./map"; +import type { ArrayType, ListItem } from "./types"; + +export class Array { + private ytype?: { doc: Doc; array: Y.YArray }; + private preliminary: any[] = []; + + get itemId() { + return this.ytype?.array.itemId; + } + + static from(items: T[]): Array { + return new Array(items); + } + + static from_ytype(ytype?: { doc: Doc; array: Y.YArray }) { + const array = new Array(); + array.ytype = ytype; + return array; + } + + constructor(items: ArrayType[] = [], ydoc?: Doc, yarray?: Y.YArray) { + this.preliminary = items; + if (ydoc) this.integrate(ydoc, yarray); + } + + integrate(ydoc: Doc, yarray?: Y.YArray): Y.YArray { + if (!this.ytype) { + this.ytype = { doc: ydoc, array: yarray || ydoc.createArray() }; + for (const item of this.preliminary) { + if (item instanceof Array) { + this.ytype.array.push(item.integrate(ydoc)); + } else { + this.ytype.array.push(item); + } + } + this.preliminary = []; + this.ytype.doc.triggerDiff(); + } + return this.ytype.array; + } + + get length(): number { + return this.ytype ? this.ytype.array.length : this.preliminary.length; + } + + get isEmpty(): boolean { + return this.ytype + ? this.ytype.array.isEmpty + : this.preliminary.length === 0; + } + + get(index: number): T { + return this.ytype ? this.ytype.array.get(index) : this.preliminary[index]; + } + + slice(start: number, end?: number): T[] { + return this.ytype + ? this.ytype.array.slice(start, end) + : this.preliminary.slice(start, end); + } + + map(callback: (...args: any[]) => any): T[] { + return this.ytype + ? this.ytype.array.map(callback) + : this.preliminary.map(callback); + } + + insert(index: number, value: ListItem): void { + if (this.ytype) { + if (value instanceof Array || value instanceof Map) { + this.ytype.array.insert(index, value.integrate(this.ytype.doc)); + } else { + this.ytype.array.insert(index, value); + } + this.ytype.doc.triggerDiff(); + } else { + this.preliminary.splice(index, 0, value); + } + } + + push(value?: ListItem): void { + if (this.ytype) { + this.ytype.array.push(value); + this.ytype.doc.triggerDiff(); + } else { + this.preliminary.push(value); + } + } + + unshift(value?: ListItem): void { + if (this.ytype) { + this.ytype.array.unshift(value); + this.ytype.doc.triggerDiff(); + } else { + this.preliminary.unshift(value); + } + } + + delete(index: number, len?: number): void { + if (this.ytype) { + this.ytype.array.delete(index, len); + this.ytype.doc.triggerDiff(); + } else { + this.preliminary.splice(index, len); + } + } + + iter(): Y.YArrayIterator { + return this.ytype + ? this.ytype.array.iter() + : this.preliminary[Symbol.iterator](); + } + + toArray(): any[] { + return this.ytype ? this.ytype.array.toArray() : this.preliminary; + } + + toJSON(): any[] { + return this.ytype ? this.ytype.array.toJSON() : this.preliminary; + } + + observe(callback: (...args: any[]) => any): void { + if (this.ytype) { + this.ytype.array.observe(callback); + } else { + throw new Error("Not implemented"); + } + } + + observeDeep(callback: (...args: any[]) => any): void { + if (this.ytype) { + this.ytype.array.observeDeep(callback); + } else { + throw new Error("Not implemented"); + } + } +} diff --git a/y-octo-node/src/doc.ts b/y-octo-node/src/doc.ts new file mode 100644 index 0000000..80524dd --- /dev/null +++ b/y-octo-node/src/doc.ts @@ -0,0 +1,85 @@ +import * as Y from "./yocto"; +import { Array } from "./array"; +import { Map } from "./map"; +import type { Text } from "./text"; + +export class Doc extends Y.Doc { + private cachedArray: globalThis.Map = new globalThis.Map(); + private cachedMap: globalThis.Map = new globalThis.Map(); + private subscribers: Set<(result: Uint8Array, origin?: unknown) => void> = + new Set(); + private lastState: Buffer | null = null; + + getArray(key: string): Array { + if (this.cachedArray.has(key)) { + return this.cachedArray.get(key)!; + } + const yarray = new Array([], this, this.getOrCreateArray(key)); + this.cachedArray.set(key, yarray); + return yarray; + } + + getMap(key: string): Map { + if (this.cachedMap.has(key)) { + return this.cachedMap.get(key)!; + } + const ymap = new Map({}, this, this.getOrCreateMap(key)); + this.cachedMap.set(key, ymap); + return ymap; + } + + getText(key: string): Text { + return this.getOrCreateText(key); + } + + triggerDiff(origin?: unknown): void { + let diff: Buffer | null = null; + if (this.lastState) { + diff = this.diff(this.lastState); + const state = this.encodeStateAsUpdateV1(); + if (!this.lastState.equals(state)) { + this.lastState = state; + } else { + return; + } + } else { + this.lastState = this.encodeStateAsUpdateV1(); + diff = this.diff(this.lastState); + } + + // skip empty diffs + if (!diff || diff.equals(new Uint8Array([0, 0]))) { + return; + } + + if (this.lastState?.length && diff?.length) { + this.subscribers.forEach((callback) => + callback(new Uint8Array(diff!), origin || this), + ); + } + } + + transact(callback: (...args: any[]) => any, origin?: unknown): void { + try { + callback(); + } finally { + this.triggerDiff(origin); + } + } + + override applyUpdate(update: Buffer): void { + this.transact(() => { + super.applyUpdate(update); + }); + } + + override onUpdate( + callback: (result: Uint8Array, origin?: unknown) => void, + ): void { + this.subscribers.add(callback); + } + + override offUpdate(): void { + this.subscribers.clear(); + } +} diff --git a/y-octo-node/src/index.ts b/y-octo-node/src/index.ts index cc4fb1d..4d74670 100644 --- a/y-octo-node/src/index.ts +++ b/y-octo-node/src/index.ts @@ -1,389 +1,6 @@ -import * as Y from "./yocto"; - -type ArrayType = - | string - | number - | any[] - | Uint8Array - | { [x: string]: any } - | null; -type ListItem = - | Array - | Map - | Text - | boolean - | number - | string - | Record - | null; - -export class Doc extends Y.Doc { - private cachedArray: globalThis.Map = new globalThis.Map(); - private cachedMap: globalThis.Map = new globalThis.Map(); - private subscribers: Set<(result: Uint8Array, origin?: unknown) => void> = - new Set(); - private lastState: Buffer | null = null; - - getArray(key: string): Array { - if (this.cachedArray.has(key)) { - return this.cachedArray.get(key)!; - } - const yarray = new Array([], this, this.getOrCreateArray(key)); - this.cachedArray.set(key, yarray); - return yarray; - } - - getMap(key: string): Map { - if (this.cachedMap.has(key)) { - return this.cachedMap.get(key)!; - } - const ymap = new Map({}, this, this.getOrCreateMap(key)); - this.cachedMap.set(key, ymap); - return ymap; - } - - getText(key: string): Text { - return this.getOrCreateText(key); - } - - triggerDiff(origin?: unknown): void { - let diff: Buffer | null = null; - if (this.lastState) { - diff = this.diff(this.lastState); - const state = this.encodeStateAsUpdateV1(); - if (!this.lastState.equals(state)) { - this.lastState = state; - } else { - return; - } - } else { - this.lastState = this.encodeStateAsUpdateV1(); - diff = this.diff(this.lastState); - } - - // skip empty diffs - if (!diff || diff.equals(new Uint8Array([0, 0]))) { - return; - } - - if (this.lastState?.length && diff?.length) { - this.subscribers.forEach((callback) => - callback(new Uint8Array(diff!), origin || this), - ); - } - } - - transact(callback: (...args: any[]) => any, origin?: unknown): void { - try { - callback(); - } finally { - this.triggerDiff(origin); - } - } - - override applyUpdate(update: Buffer): void { - this.transact(() => { - super.applyUpdate(update); - }); - } - - override onUpdate( - callback: (result: Uint8Array, origin?: unknown) => void, - ): void { - this.subscribers.add(callback); - } - - override offUpdate(): void { - this.subscribers.clear(); - } -} - -export class Protocol extends Y.YProtocol { - constructor(private readonly doc: Doc) { - super(doc); - } - - override applySyncStep(buffer: Buffer): Buffer | null { - try { - return super.applySyncStep(buffer); - } finally { - this.doc.triggerDiff(this.doc); - } - } -} - -export class Array { - private ytype?: { doc: Doc; array: Y.YArray }; - private preliminary: any[] = []; - - get itemId() { - return this.ytype?.array.itemId; - } - - static from(items: T[]): Array { - return new Array(items); - } - - static from_ytype(ytype?: { doc: Doc; array: Y.YArray }) { - const array = new Array(); - array.ytype = ytype; - return array; - } - - constructor(items: ArrayType[] = [], ydoc?: Doc, yarray?: Y.YArray) { - this.preliminary = items; - if (ydoc) this.integrate(ydoc, yarray); - } - - integrate(ydoc: Doc, yarray?: Y.YArray): Y.YArray { - if (!this.ytype) { - this.ytype = { doc: ydoc, array: yarray || ydoc.createArray() }; - for (const item of this.preliminary) { - if (item instanceof Array) { - this.ytype.array.push(item.integrate(ydoc)); - } else { - this.ytype.array.push(item); - } - } - this.preliminary = []; - this.ytype.doc.triggerDiff(); - } - return this.ytype.array; - } - - get length(): number { - return this.ytype ? this.ytype.array.length : this.preliminary.length; - } - - get isEmpty(): boolean { - return this.ytype - ? this.ytype.array.isEmpty - : this.preliminary.length === 0; - } - - get(index: number): T { - return this.ytype ? this.ytype.array.get(index) : this.preliminary[index]; - } - - slice(start: number, end?: number): T[] { - return this.ytype - ? this.ytype.array.slice(start, end) - : this.preliminary.slice(start, end); - } - - map(callback: (...args: any[]) => any): T[] { - return this.ytype - ? this.ytype.array.map(callback) - : this.preliminary.map(callback); - } - - insert(index: number, value: ListItem): void { - if (this.ytype) { - if (value instanceof Array || value instanceof Map) { - this.ytype.array.insert(index, value.integrate(this.ytype.doc)); - } else { - this.ytype.array.insert(index, value); - } - this.ytype.doc.triggerDiff(); - } else { - this.preliminary.splice(index, 0, value); - } - } - - push(value?: ListItem): void { - if (this.ytype) { - this.ytype.array.push(value); - this.ytype.doc.triggerDiff(); - } else { - this.preliminary.push(value); - } - } - - unshift(value?: ListItem): void { - if (this.ytype) { - this.ytype.array.unshift(value); - this.ytype.doc.triggerDiff(); - } else { - this.preliminary.unshift(value); - } - } - - delete(index: number, len?: number): void { - if (this.ytype) { - this.ytype.array.delete(index, len); - this.ytype.doc.triggerDiff(); - } else { - this.preliminary.splice(index, len); - } - } - - iter(): Y.YArrayIterator { - return this.ytype - ? this.ytype.array.iter() - : this.preliminary[Symbol.iterator](); - } - - toArray(): any[] { - return this.ytype ? this.ytype.array.toArray() : this.preliminary; - } - - toJSON(): any[] { - return this.ytype ? this.ytype.array.toJSON() : this.preliminary; - } - - observe(callback: (...args: any[]) => any): void { - if (this.ytype) { - this.ytype.array.observe(callback); - } else { - throw new Error("Not implemented"); - } - } - - observeDeep(callback: (...args: any[]) => any): void { - if (this.ytype) { - this.ytype.array.observeDeep(callback); - } else { - throw new Error("Not implemented"); - } - } -} - -export class Map { - private ytype?: { doc: Doc; map: Y.YMap }; - private preliminary: Record = {}; - - static from_ytype(ytype?: { doc: Doc; map: Y.YMap }) { - const map = new Map(); - map.ytype = ytype; - return map; - } - - constructor(items: Record = {}, ydoc?: Doc, ymap?: Y.YMap) { - this.preliminary = items; - if (ydoc) this.integrate(ydoc, ymap); - } - - integrate(ydoc: Doc, ymap?: Y.YMap): Y.YMap { - if (!this.ytype) { - this.ytype = { doc: ydoc, map: ymap || ydoc.createMap() }; - for (const [key, val] of Object.entries(this.preliminary)) { - if (val instanceof Array) { - this.ytype.map.set(key, val.integrate(ydoc)); - } else { - this.ytype.map.set(key, val); - } - } - this.preliminary = {}; - this.ytype.doc.triggerDiff(); - } - return this.ytype.map; - } - - get length(): number { - return this.ytype - ? this.ytype.map.length - : Object.keys(this.preliminary).length; - } - - get size(): number { - return this.length; - } - - get isEmpty(): boolean { - return this.ytype ? this.ytype.map.isEmpty : this.length === 0; - } - - get itemId(): Y.YId | null { - return this.ytype?.map.itemId || null; - } - - get(key: string): T { - if (this.ytype) { - const ret = this.ytype.map.get(key); - if (ret) { - if (ret instanceof Y.YArray) { - return Array.from_ytype({ doc: this.ytype.doc, array: ret }) as T; - } else if (ret instanceof Y.YMap) { - return Map.from_ytype({ doc: this.ytype.doc, map: ret }) as T; - } - } - return ret as T; - } else { - return this.preliminary[key]; - } - } - - set(key: string, value: T) { - if (this.ytype) { - if (value instanceof Array || value instanceof Map) { - this.ytype.map.set(key, value.integrate(this.ytype.doc)); - } else { - this.ytype.map.set(key, value); - } - this.ytype.doc.triggerDiff(); - } else { - this.preliminary[key] = value; - } - return value; - } - - delete(key: string): void { - if (this.ytype) { - this.ytype.map.delete(key); - this.ytype.doc.triggerDiff(); - } else { - delete this.preliminary[key]; - } - } - - clear(): void { - if (this.ytype) { - this.ytype.map.clear(); - this.ytype.doc.triggerDiff(); - } else { - this.preliminary = {}; - } - } - - toJSON(): Record { - return this.ytype - ? this.ytype.map.toJSON() - : JSON.parse(JSON.stringify(this.preliminary)); - } - - entries(): Y.YMapEntriesIterator { - return this.ytype - ? this.ytype.map.entries() - : Object.entries(this.preliminary); - } - - keys(): Y.YMapKeyIterator { - return this.ytype ? this.ytype.map.keys() : Object.keys(this.preliminary); - } - - values(): Y.YMapValuesIterator { - return this.ytype - ? this.ytype.map.values() - : Object.values(this.preliminary); - } - - observe(callback: (...args: any[]) => any): void { - if (this.ytype) { - this.ytype.map.observe(callback); - } else { - throw new Error("Not implemented"); - } - } - - observeDeep(callback: (...args: any[]) => any): void { - if (this.ytype) { - this.ytype.map.observeDeep(callback); - } else { - throw new Error("Not implemented"); - } - } -} - -export class Text extends Y.YText {} - export * from "./yocto"; +export { Doc } from "./doc"; +export { Array } from "./array"; +export { Map } from "./map"; +export { Text } from "./text"; +export { Protocol } from "./protocol"; diff --git a/y-octo-node/src/map.ts b/y-octo-node/src/map.ts new file mode 100644 index 0000000..39ca2b1 --- /dev/null +++ b/y-octo-node/src/map.ts @@ -0,0 +1,140 @@ +import * as Y from "./yocto"; +import { Array } from "./array"; +import type { Doc } from "./doc"; +import type { ListItem } from "./types"; + +export class Map { + private ytype?: { doc: Doc; map: Y.YMap }; + private preliminary: Record = {}; + + static from_ytype(ytype?: { doc: Doc; map: Y.YMap }) { + const map = new Map(); + map.ytype = ytype; + return map; + } + + constructor(items: Record = {}, ydoc?: Doc, ymap?: Y.YMap) { + this.preliminary = items; + if (ydoc) this.integrate(ydoc, ymap); + } + + integrate(ydoc: Doc, ymap?: Y.YMap): Y.YMap { + if (!this.ytype) { + this.ytype = { doc: ydoc, map: ymap || ydoc.createMap() }; + for (const [key, val] of Object.entries(this.preliminary)) { + if (val instanceof Array) { + this.ytype.map.set(key, val.integrate(ydoc)); + } else { + this.ytype.map.set(key, val); + } + } + this.preliminary = {}; + this.ytype.doc.triggerDiff(); + } + return this.ytype.map; + } + + get length(): number { + return this.ytype + ? this.ytype.map.length + : Object.keys(this.preliminary).length; + } + + get size(): number { + return this.length; + } + + get isEmpty(): boolean { + return this.ytype ? this.ytype.map.isEmpty : this.length === 0; + } + + get itemId(): Y.YId | null { + return this.ytype?.map.itemId || null; + } + + get(key: string): T { + if (this.ytype) { + const ret = this.ytype.map.get(key); + if (ret) { + if (ret instanceof Y.YArray) { + return Array.from_ytype({ doc: this.ytype.doc, array: ret }) as T; + } else if (ret instanceof Y.YMap) { + return Map.from_ytype({ doc: this.ytype.doc, map: ret }) as T; + } + } + return ret as T; + } else { + return this.preliminary[key]; + } + } + + set(key: string, value: T) { + if (this.ytype) { + if (value instanceof Array || value instanceof Map) { + this.ytype.map.set(key, value.integrate(this.ytype.doc)); + } else { + this.ytype.map.set(key, value); + } + this.ytype.doc.triggerDiff(); + } else { + this.preliminary[key] = value; + } + return value; + } + + delete(key: string): void { + if (this.ytype) { + this.ytype.map.delete(key); + this.ytype.doc.triggerDiff(); + } else { + delete this.preliminary[key]; + } + } + + clear(): void { + if (this.ytype) { + this.ytype.map.clear(); + this.ytype.doc.triggerDiff(); + } else { + this.preliminary = {}; + } + } + + toJSON(): Record { + return this.ytype + ? this.ytype.map.toJSON() + : JSON.parse(JSON.stringify(this.preliminary)); + } + + entries(): Y.YMapEntriesIterator { + return this.ytype + ? this.ytype.map.entries() + : Object.entries(this.preliminary); + } + + keys(): Y.YMapKeyIterator { + return this.ytype ? this.ytype.map.keys() : Object.keys(this.preliminary); + } + + values(): Y.YMapValuesIterator { + return this.ytype + ? this.ytype.map.values() + : Object.values(this.preliminary); + } + + observe(callback: (...args: any[]) => any): void { + if (this.ytype) { + this.ytype.map.observe(callback); + } else { + throw new Error("Not implemented"); + } + } + + observeDeep(callback: (...args: any[]) => any): void { + if (this.ytype) { + this.ytype.map.observeDeep(callback); + } else { + throw new Error("Not implemented"); + } + } +} diff --git a/y-octo-node/src/protocol.ts b/y-octo-node/src/protocol.ts new file mode 100644 index 0000000..1865e46 --- /dev/null +++ b/y-octo-node/src/protocol.ts @@ -0,0 +1,16 @@ +import * as Y from "./yocto"; +import type { Doc } from "./doc"; + +export class Protocol extends Y.YProtocol { + constructor(private readonly doc: Doc) { + super(doc); + } + + override applySyncStep(buffer: Buffer): Buffer | null { + try { + return super.applySyncStep(buffer); + } finally { + this.doc.triggerDiff(this.doc); + } + } +} diff --git a/y-octo-node/src/text.ts b/y-octo-node/src/text.ts new file mode 100644 index 0000000..7a59c4e --- /dev/null +++ b/y-octo-node/src/text.ts @@ -0,0 +1,3 @@ +import * as Y from "./yocto"; + +export class Text extends Y.YText {} diff --git a/y-octo-node/src/types.ts b/y-octo-node/src/types.ts new file mode 100644 index 0000000..fe6321d --- /dev/null +++ b/y-octo-node/src/types.ts @@ -0,0 +1,19 @@ +import type { Array } from "./array"; +import type { Map } from "./map"; + +export type ArrayType = + | string + | number + | any[] + | Uint8Array + | { [x: string]: any } + | null; +export type ListItem = + | Array + | Map + | Text + | boolean + | number + | string + | Record + | null; From 8e4dcdcf5858d22a9390771f24b57d6f050aef78 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Thu, 12 Sep 2024 16:06:31 +0800 Subject: [PATCH 70/75] feat: make client id conflict behavior same as yjs --- y-octo-node/native/src/doc.rs | 9 +++++++++ y-octo-node/tests/doc.spec.ts | 4 +++- y-octo-node/tests/yjs/doc.spec.ts | 8 ++++---- y-octo/src/doc/document.rs | 4 ++++ 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/y-octo-node/native/src/doc.rs b/y-octo-node/native/src/doc.rs index e283b80..8ee1748 100644 --- a/y-octo-node/native/src/doc.rs +++ b/y-octo-node/native/src/doc.rs @@ -114,8 +114,17 @@ impl YDoc { #[napi] pub fn apply_update(&mut self, update: JsBuffer) -> Result<()> { + let client = self.doc.client(); + let before_current_state = self.doc.get_state_vector().get(&client); + self.doc.apply_update_from_binary_v1(update)?; + // if update received from remote and current client state has been changed + // that means another client using same client id, we need to change the client id to avoid conflict + if self.doc.get_state_vector().get(&client) != before_current_state { + self.doc.renew_client(); + } + Ok(()) } diff --git a/y-octo-node/tests/doc.spec.ts b/y-octo-node/tests/doc.spec.ts index 10dbe0d..83e657c 100644 --- a/y-octo-node/tests/doc.spec.ts +++ b/y-octo-node/tests/doc.spec.ts @@ -44,7 +44,9 @@ test("y-octo doc update should be apply", (t) => { let map2 = doc2.getOrCreateMap("map"); let text2 = doc2.getOrCreateText("text"); - t.is(doc2.clientId, client_id); + // after apply update that include same client id's change + // the client id should be changed + t.not(doc2.clientId, client_id); t.is(array2.length, 4); t.is(array2.get(0), true); t.is(array2.get(1), false); diff --git a/y-octo-node/tests/yjs/doc.spec.ts b/y-octo-node/tests/yjs/doc.spec.ts index a656985..6f3f0f6 100644 --- a/y-octo-node/tests/yjs/doc.spec.ts +++ b/y-octo-node/tests/yjs/doc.spec.ts @@ -41,13 +41,13 @@ test.skip("testOriginInTransaction", (t) => { /** * Client id should be changed when an instance receives updates from another client using the same client id. */ -test.skip("testClientIdDuplicateChange", (t) => { +test("testClientIdDuplicateChange", (t) => { const doc1 = new Y.Doc(0); const doc2 = new Y.Doc(0); - t.assert(doc2.clientID === doc1.clientID); + t.assert(doc2.clientId === doc1.clientId); doc1.getArray("a").insert(0, [1, 2]); - Y.applyUpdate(doc2, Y.encodeStateAsUpdate(doc1)); - t.assert(doc2.clientID !== doc1.clientID); + Y.applyUpdate(doc2, Y.encodeStateAsUpdate(doc1), false); + t.assert(doc2.clientId !== doc1.clientId); }); test.skip("testGetTypeEmptyId", (t) => { diff --git a/y-octo/src/doc/document.rs b/y-octo/src/doc/document.rs index 7d8b060..f09b392 100644 --- a/y-octo/src/doc/document.rs +++ b/y-octo/src/doc/document.rs @@ -162,6 +162,10 @@ impl Doc { self.client_id = client_id; } + pub fn renew_client(&mut self) { + self.client_id = prefer_small_random(); + } + pub fn clients(&self) -> Vec { self.store.read().unwrap().clients() } From e6a7f0261faba46dad72fa8a4b7d3519803a1bab Mon Sep 17 00:00:00 2001 From: DarkSky Date: Thu, 12 Sep 2024 16:20:09 +0800 Subject: [PATCH 71/75] feat: allow empty root id --- y-octo-node/src/doc.ts | 6 +++--- y-octo-node/tests/yjs/doc.spec.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/y-octo-node/src/doc.ts b/y-octo-node/src/doc.ts index 80524dd..e35a02b 100644 --- a/y-octo-node/src/doc.ts +++ b/y-octo-node/src/doc.ts @@ -10,7 +10,7 @@ export class Doc extends Y.Doc { new Set(); private lastState: Buffer | null = null; - getArray(key: string): Array { + getArray(key = ""): Array { if (this.cachedArray.has(key)) { return this.cachedArray.get(key)!; } @@ -19,7 +19,7 @@ export class Doc extends Y.Doc { return yarray; } - getMap(key: string): Map { + getMap(key = ""): Map { if (this.cachedMap.has(key)) { return this.cachedMap.get(key)!; } @@ -28,7 +28,7 @@ export class Doc extends Y.Doc { return ymap; } - getText(key: string): Text { + getText(key = ""): Text { return this.getOrCreateText(key); } diff --git a/y-octo-node/tests/yjs/doc.spec.ts b/y-octo-node/tests/yjs/doc.spec.ts index 6f3f0f6..dc8a74a 100644 --- a/y-octo-node/tests/yjs/doc.spec.ts +++ b/y-octo-node/tests/yjs/doc.spec.ts @@ -50,7 +50,7 @@ test("testClientIdDuplicateChange", (t) => { t.assert(doc2.clientId !== doc1.clientId); }); -test.skip("testGetTypeEmptyId", (t) => { +test("testGetTypeEmptyId", (t) => { const doc1 = new Y.Doc(); doc1.getText("").insert(0, "h"); doc1.getText().insert(1, "i"); From e95bef9c56516e734c9d22a1001381a1308b300d Mon Sep 17 00:00:00 2001 From: DarkSky Date: Thu, 12 Sep 2024 18:46:33 +0800 Subject: [PATCH 72/75] fix: ci --- .github/workflows/y-octo-node.yml | 4 ++-- y-octo-node/native/src/doc.rs | 3 ++- y-octo-node/package.json | 6 +++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/y-octo-node.yml b/.github/workflows/y-octo-node.yml index cfc7151..3b8c2b8 100644 --- a/.github/workflows/y-octo-node.yml +++ b/.github/workflows/y-octo-node.yml @@ -52,7 +52,7 @@ jobs: uses: actions/upload-artifact@v3 with: name: y-octo.${{ matrix.settings.target }}.node - path: ./y-octo-node/*.node + path: ./y-octo-node/dist if-no-files-found: error test-node: @@ -104,7 +104,7 @@ jobs: uses: actions/download-artifact@v3 with: name: y-octo.${{ matrix.settings.target }}.node - path: ./y-octo-node + path: ./y-octo-node/dist - name: Run node binding tests run: ls -lah & ls -lah tests working-directory: y-octo-node diff --git a/y-octo-node/native/src/doc.rs b/y-octo-node/native/src/doc.rs index 8ee1748..75f8c2e 100644 --- a/y-octo-node/native/src/doc.rs +++ b/y-octo-node/native/src/doc.rs @@ -120,7 +120,8 @@ impl YDoc { self.doc.apply_update_from_binary_v1(update)?; // if update received from remote and current client state has been changed - // that means another client using same client id, we need to change the client id to avoid conflict + // that means another client using same client id, we need to change the client + // id to avoid conflict if self.doc.get_state_vector().get(&client) != before_current_state { self.doc.renew_client(); } diff --git a/y-octo-node/package.json b/y-octo-node/package.json index 7494b01..5ac019d 100644 --- a/y-octo-node/package.json +++ b/y-octo-node/package.json @@ -40,13 +40,13 @@ "artifacts": "napi artifacts", "build": "yarn build:binding && yarn build:js", "build:debug": "yarn build:binding:debug && yarn build:js", - "build:js": "pkgroll", + "build:js": "pkgroll -- -", "build:binding": "yarn build:binding:debug --release", "build:binding:debug": "napi build --platform --no-const-enum --manifest-path native/Cargo.toml --dts ../src/yocto.d.ts --js ../src/yocto.js -o ./dist", "universal": "napi universal", "test": "ava --concurrency 1 --serial --timeout 5s", - "test:watch": "yarn exec tsx ./scripts/run-test.mts all --watch", - "test:coverage": "NODE_OPTIONS=\"--import tsx\" c8 node ./scripts/run-test.mts all", + "test:watch": "yarn test --watch", + "test:coverage": "c8 ava --concurrency 1 --serial", "version": "napi version" }, "version": "0.0.1", From 63d0e0e0a122c098a58fe1b2439d40260be6bc6a Mon Sep 17 00:00:00 2001 From: DarkSky Date: Thu, 17 Oct 2024 19:04:28 +0800 Subject: [PATCH 73/75] feat: add changed record --- y-octo/src/doc/store.rs | 93 +++++++++++++++++++++++++++++++++++-- y-octo/src/doc/types/mod.rs | 14 +++++- 2 files changed, 101 insertions(+), 6 deletions(-) diff --git a/y-octo/src/doc/store.rs b/y-octo/src/doc/store.rs index 2169203..f784c53 100644 --- a/y-octo/src/doc/store.rs +++ b/y-octo/src/doc/store.rs @@ -10,6 +10,8 @@ use crate::{ sync::{Arc, RwLock, RwLockWriteGuard, Weak}, }; +type ChangedTypeRefs = HashMap>; + unsafe impl Send for DocStore {} unsafe impl Sync for DocStore {} @@ -26,6 +28,8 @@ pub(crate) struct DocStore { pub dangling_types: HashMap, pub pending: Option, pub last_optimized_state: StateVector, + // changed item's parent, value is the parent's sub key if exists + pub changed: ChangedTypeRefs, } pub(crate) type StoreRef = Arc>; @@ -583,6 +587,9 @@ impl DocStore { } parent_lock.take(); + + // mark changed item's parent + Self::mark_changed(&mut self.changed, ty.clone(), this.parent_sub.clone()); } else { // if parent not exists, integrate GC node instead // don't delete it because it may referenced by other nodes @@ -605,13 +612,18 @@ impl DocStore { pub fn delete_item(&mut self, item: &Item, parent: Option<&mut YType>) { let mut pending_delete_sets = HashMap::new(); - Self::delete_item_inner(&mut pending_delete_sets, item, parent); + Self::delete_item_inner(&mut pending_delete_sets, &mut self.changed, item, parent); for (client, ranges) in pending_delete_sets { self.delete_set.batch_add_ranges(client, ranges); } } - fn delete_item_inner(delete_set: &mut HashMap>>, item: &Item, parent: Option<&mut YType>) { + fn delete_item_inner( + delete_set: &mut HashMap>>, + changed: &mut ChangedTypeRefs, + item: &Item, + parent: Option<&mut YType>, + ) { // 1. mark item as deleted, if item is gced, return if !item.delete() { return; @@ -643,7 +655,7 @@ impl DocStore { let mut item_ref = ty.start.clone(); while let Some(item) = item_ref.get() { if !item.deleted() { - Self::delete_item_inner(delete_set, item, Some(&mut ty)); + Self::delete_item_inner(delete_set, changed, item, Some(&mut ty)); } item_ref = item.right.clone(); @@ -653,7 +665,7 @@ impl DocStore { for item in map_values { if let Some(item) = item.get() { if !item.deleted() { - Self::delete_item_inner(delete_set, item, Some(&mut ty)); + Self::delete_item_inner(delete_set, changed, item, Some(&mut ty)); } } } @@ -664,6 +676,11 @@ impl DocStore { } _ => {} } + + // mark deleted item's parent + if let Some(Parent::Type(ty)) = &item.parent { + Self::mark_changed(changed, ty.clone(), item.parent_sub.clone()); + } } pub fn delete_node(&mut self, struct_info: &Node, parent: Option<&mut YType>) { @@ -707,7 +724,7 @@ impl DocStore { DocStore::split_node_at(items, idx, end - id.clock)?; } - Self::delete_item_inner(&mut pending_delete_sets, item, None); + Self::delete_item_inner(&mut pending_delete_sets, &mut self.changed, item, None); } } } else { @@ -762,6 +779,20 @@ impl DocStore { Ok(update) } + fn mark_changed(changed: &mut ChangedTypeRefs, parent: YTypeRef, parent_sub: Option) { + if parent.inner.is_some() { + let vec = changed.entry(parent).or_insert_with(Vec::new); + if let Some(parent_sub) = parent_sub { + // only record the sub key if exists + vec.push(parent_sub); + } + } + } + + pub fn get_changed(&mut self) -> ChangedTypeRefs { + mem::replace(&mut self.changed, HashMap::new()) + } + fn diff_structs(map: &ClientMap>, sv: &StateVector) -> JwstCodecResult>> { let local_state_vector = Self::items_as_state_vector(map); let diff = Self::diff_state_vectors(&local_state_vector, sv); @@ -1161,6 +1192,58 @@ mod tests { }); } + #[test] + fn should_mark_changed_items() { + loom_model!({ + let doc = DocOptions::new().with_client_id(1).build(); + + let mut arr = doc.get_or_create_array("arr").unwrap(); + let mut text = doc.create_text().unwrap(); + let mut map = doc.create_map().unwrap(); + + arr.insert(0, Value::from(text.clone())).unwrap(); + arr.insert(1, Value::from(map.clone())).unwrap(); + { + let changed = doc.store.write().unwrap().get_changed(); + // for array, we will only record the type ref itself + assert_eq!(changed.len(), 1); + assert_eq!(changed.get(&arr.0), Some(&vec![])); + } + + text.insert(0, "hello world").unwrap(); + text.remove(5, 6).unwrap(); + { + let changed = doc.store.write().unwrap().get_changed(); + assert_eq!(changed.len(), 1); + assert_eq!(changed.get(&text.0), Some(&vec![])); + } + + map.insert("key".into(), 123).unwrap(); + { + let changed = doc.store.write().unwrap().get_changed(); + assert_eq!(changed.len(), 1); + assert_eq!(changed.get(&map.0), Some(&vec!["key".into()])); + } + + map.remove("key"); + { + let changed = doc.store.write().unwrap().get_changed(); + assert_eq!(changed.len(), 1); + assert_eq!(changed.get(&map.0), Some(&vec!["key".into()])); + } + + arr.remove(0, 1).unwrap(); + { + let changed = doc.store.write().unwrap().get_changed(); + assert_eq!(changed.len(), 2); + // text's children mark parent(text) changed + assert_eq!(changed.get(&text.0), Some(&vec![])); + // text mark parent(arr) changed + assert_eq!(changed.get(&arr.0), Some(&vec![])); + } + }); + } + #[test] fn should_replace_gc_item_with_content_deleted() { loom_model!({ diff --git a/y-octo/src/doc/types/mod.rs b/y-octo/src/doc/types/mod.rs index 4934690..eaec0ef 100644 --- a/y-octo/src/doc/types/mod.rs +++ b/y-octo/src/doc/types/mod.rs @@ -5,7 +5,11 @@ mod text; mod value; mod xml; -use std::{collections::hash_map::Entry, sync::Weak}; +use std::{ + collections::hash_map::Entry, + hash::{Hash, Hasher}, + sync::Weak, +}; pub use array::*; use list::*; @@ -62,6 +66,14 @@ impl PartialEq for YTypeRef { } } +impl Eq for YTypeRef {} + +impl Hash for YTypeRef { + fn hash(&self, state: &mut H) { + self.inner.ptr().hash(state); + } +} + impl YType { pub fn new(kind: YTypeKind, tag_name: Option) -> Self { YType { From ac49e7e2cf55095cff02f092838958587797a380 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Thu, 17 Oct 2024 19:05:20 +0800 Subject: [PATCH 74/75] feat: add reset_changed --- y-octo/src/doc/store.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/y-octo/src/doc/store.rs b/y-octo/src/doc/store.rs index f784c53..b3c74ce 100644 --- a/y-octo/src/doc/store.rs +++ b/y-octo/src/doc/store.rs @@ -789,6 +789,10 @@ impl DocStore { } } + pub fn reset_changed(&mut self) { + self.changed.clear(); + } + pub fn get_changed(&mut self) -> ChangedTypeRefs { mem::replace(&mut self.changed, HashMap::new()) } From 6ac18efad4fb60d1a858b10b3400f3d997d1b938 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Thu, 17 Oct 2024 23:33:34 +0800 Subject: [PATCH 75/75] feat: init batch commit api --- y-octo/src/doc/batch.rs | 122 +++++++++++++++++++++++++++++++++++++ y-octo/src/doc/document.rs | 15 ++++- y-octo/src/doc/mod.rs | 2 + y-octo/src/doc/store.rs | 2 +- y-octo/src/lib.rs | 6 +- 5 files changed, 142 insertions(+), 5 deletions(-) create mode 100644 y-octo/src/doc/batch.rs diff --git a/y-octo/src/doc/batch.rs b/y-octo/src/doc/batch.rs new file mode 100644 index 0000000..63c9639 --- /dev/null +++ b/y-octo/src/doc/batch.rs @@ -0,0 +1,122 @@ +use super::*; + +#[derive(Debug, PartialEq)] +pub struct Batch { + doc: Doc, + before_state: StateVector, + after_state: StateVector, + changed: HashMap>, +} + +impl Batch { + pub fn new(doc: Doc) -> Self { + let current_state = doc.get_state_vector(); + + Batch { + doc, + before_state: current_state.clone(), + after_state: current_state, + changed: HashMap::new(), + } + } + + pub fn with_batch(&mut self, f: F) -> T + where + F: FnOnce(Doc) -> T, + { + let ret = f(self.doc.clone()); + for (k, v) in self.doc.get_changed() { + self.changed.entry(k).or_insert_with(Vec::new).extend(v.iter().cloned()); + } + ret + } +} + +pub fn batch_commit(mut doc: Doc, f: F) -> Option +where + F: FnOnce(Doc) -> T, +{ + // Initialize batch cleanups list + let mut batch_cleanups = vec![]; + + // Initial call and result initialization + let mut initial_call = false; + + { + if doc.batch.is_none() { + initial_call = true; + + // Start a new batch + let batch = Batch::new(doc.clone()); + doc.batch = Somr::new(batch); + batch_cleanups.push(doc.batch.clone()); + } + } + + let Some(batch) = doc.batch.get_mut() else { + return None; + }; + + let result = Some(batch.with_batch(f)); + + if initial_call { + if let Some(current_batch) = doc.batch.get() { + if Some(current_batch) == batch_cleanups[0].get() { + // Process observer calls and perform cleanup if this is the initial call + cleanup_batches(&mut batch_cleanups); + doc.batch.swap_take(); + } + } + } + + result +} + +fn cleanup_batches(batch_cleanups: &mut Vec>) { + for batch in batch_cleanups.drain(..) { + if let Some(batch) = batch.get() { + println!("changed: {:?}", batch.changed); + } else { + panic!("Batch not initialized"); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn should_get_changed_items() { + // loom_model!({ + let doc = DocOptions::new().with_client_id(1).build(); + + batch_commit(doc.clone(), |d| { + let mut arr = d.get_or_create_array("arr").unwrap(); + let mut text = d.create_text().unwrap(); + let mut map = d.create_map().unwrap(); + + batch_commit(doc.clone(), |_| { + arr.insert(0, Value::from(text.clone())).unwrap(); + arr.insert(1, Value::from(map.clone())).unwrap(); + }); + + batch_commit(doc.clone(), |_| { + text.insert(0, "hello world").unwrap(); + text.remove(5, 6).unwrap(); + }); + + batch_commit(doc.clone(), |_| { + map.insert("key".into(), 123).unwrap(); + }); + + batch_commit(doc.clone(), |_| { + map.remove("key"); + }); + + batch_commit(doc.clone(), |_| { + arr.remove(0, 1).unwrap(); + }); + }); + } +} diff --git a/y-octo/src/doc/document.rs b/y-octo/src/doc/document.rs index f09b392..0513240 100644 --- a/y-octo/src/doc/document.rs +++ b/y-octo/src/doc/document.rs @@ -1,4 +1,11 @@ -use super::{history::StoreHistory, publisher::DocPublisher, store::StoreRef, *}; +use std::collections::HashMap; + +use super::{ + history::StoreHistory, + publisher::DocPublisher, + store::{ChangedTypeRefs, StoreRef}, + *, +}; use crate::sync::{Arc, RwLock}; #[cfg(feature = "debug")] @@ -116,6 +123,7 @@ pub struct Doc { pub(crate) store: StoreRef, pub publisher: Arc, + pub(crate) batch: Somr, } unsafe impl Send for Doc {} @@ -147,6 +155,7 @@ impl Doc { opts: options, store, publisher, + batch: Somr::none(), } } @@ -189,6 +198,10 @@ impl Doc { } } + pub fn get_changed(&self) -> ChangedTypeRefs { + self.store.write().unwrap().get_changed() + } + pub fn store_compare(&self, other: &Doc) -> bool { let store = self.store.read().unwrap(); let other_store = other.store.read().unwrap(); diff --git a/y-octo/src/doc/mod.rs b/y-octo/src/doc/mod.rs index 608f654..371813b 100644 --- a/y-octo/src/doc/mod.rs +++ b/y-octo/src/doc/mod.rs @@ -1,4 +1,5 @@ mod awareness; +mod batch; mod codec; mod common; mod document; @@ -11,6 +12,7 @@ mod utils; pub use ahash::{HashMap, HashMapExt, HashSet, HashSetExt}; pub use awareness::{Awareness, AwarenessEvent}; +pub use batch::{batch_commit, Batch}; pub use codec::*; pub use common::*; pub use document::{Doc, DocOptions}; diff --git a/y-octo/src/doc/store.rs b/y-octo/src/doc/store.rs index b3c74ce..cd85004 100644 --- a/y-octo/src/doc/store.rs +++ b/y-octo/src/doc/store.rs @@ -10,7 +10,7 @@ use crate::{ sync::{Arc, RwLock, RwLockWriteGuard, Weak}, }; -type ChangedTypeRefs = HashMap>; +pub type ChangedTypeRefs = HashMap>; unsafe impl Send for DocStore {} unsafe impl Sync for DocStore {} diff --git a/y-octo/src/lib.rs b/y-octo/src/lib.rs index f242d02..5e00b43 100644 --- a/y-octo/src/lib.rs +++ b/y-octo/src/lib.rs @@ -6,9 +6,9 @@ mod sync; pub use codec::*; pub use doc::{ - encode_awareness_as_message, encode_update_as_message, merge_updates_v1, prefer_small_random, Any, Array, - Awareness, AwarenessEvent, Client, ClientMap, Clock, CrdtRead, CrdtReader, CrdtWrite, CrdtWriter, DeleteSet, Doc, - DocOptions, EntriesIterator, HashMap as AHashMap, HashMapExt, History, HistoryOptions, Id, KeysIterator, Map, + batch_commit, encode_awareness_as_message, encode_update_as_message, merge_updates_v1, prefer_small_random, Any, + Array, Awareness, AwarenessEvent, Client, ClientMap, Clock, CrdtRead, CrdtReader, CrdtWrite, CrdtWriter, DeleteSet, + Doc, DocOptions, EntriesIterator, HashMap as AHashMap, HashMapExt, History, HistoryOptions, Id, KeysIterator, Map, RawDecoder, RawEncoder, StateVector, StoreHistory, Text, Update, Value, ValuesIterator, }; pub(crate) use doc::{Content, Item};