FRE-600: Fix code review blockers

- Consolidated duplicate UndoManagers to single instance
- Fixed connection promise to only resolve on 'connected' status
- Fixed WebSocketProvider import (WebsocketProvider)
- Added proper doc.destroy() cleanup
- Renamed isPresenceInitialized property to avoid conflict

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
2026-04-25 00:08:01 -04:00
parent 65b552bb08
commit 7c684a42cc
48450 changed files with 5679671 additions and 383 deletions

22
node_modules/y-leveldb/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2020
- Kevin Jahns <kevin.jahns@rwth-aachen.de>.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

114
node_modules/y-leveldb/README.md generated vendored Normal file
View File

@@ -0,0 +1,114 @@
# LevelDB database adapter for [Yjs](https://github.com/yjs/yjs)
LevelDB is a fast embedded database. It is the underlying technology of IndexedDB.
Internally, y-leveldb uses [`level`](https://github.com/Level/level) which
allows to exchange the storage medium for a different supported database.
Hence this adapter also supports rocksdb, lmdb, and many more..
* Persistent storage for the server
* Exchangeable storage medium
* Can be used in [y-websocket](https://github.com/yjs/y-websocket)
* A single y-leveldb instance can handle many documents.
## Use it
```sh
npm install y-leveldb --save
```
```js
import { LeveldbPersistence } from 'y-leveldb'
const persistence = new LeveldbPersistence('./storage-location')
const ydoc = new Y.Doc()
ydoc.getArray('arr').insert(0, [1, 2, 3])
ydoc.getArray('arr').toArray() // => [1, 2, 3]
// store document updates retrieved from other clients
persistence.storeUpdate('my-doc', Y.encodeStateAsUpdate(ydoc))
// when you want to sync, or store data to a database,
// retrieve the temporary Y.Doc to consume data
persistence.getYDoc('my-doc').getArray('arr') // [1, 2, 3]
```
## API
### `persistence = LeveldbPersistence(storageLocation, [{ [level] }])`
Create a y-leveldb persistence instance.
You can use any levelup-compatible adapter.
```js
import { LeveldbPersistence } from 'y-leveldb'
import level from 'level-mem'
const persistence = new LeveldbPersistence('./storage-location', { level })
```
#### `persistence.getYDoc(docName: string): Promise<Y.Doc>`
Create a Y.Doc instance with the data persistet in leveldb. Use this to
temporarily create a Yjs document to sync changes or extract data.
#### `persistence.storeUpdate(docName: string, update: Uint8Array): Promise`
Store a single document update to the database.
#### `persistence.getStateVector(docName: string): Promise<Uint8Array>`
The state vector (describing the state of the persisted document - see
[Yjs docs](https://github.com/yjs/yjs#Document-Updates)) is maintained in a separate
field and constantly updated.
This allows you to sync changes without actually creating a Yjs document.
#### `persistence.getDiff(docName: string, stateVector: Uint8Array): Promise<Uint8Array>`
Get the differences directly from the database. The same as
`Y.encodeStateAsUpdate(ydoc, stateVector)`.
#### `persistence.clearDocument(docName: string): Promise`
Delete a document, and all associated data from the database.
#### `persistence.setMeta(docName: string, metaKey: string, value: any): Promise`
Persist some meta information in the database and associate it with a document.
It is up to you what you store here. You could, for example, store credentials
here.
#### `persistence.getMeta(docName: string, metaKey: string): Promise<any|undefined>`
Retrieve a store meta value from the database. Returns undefined if the
`metaKey` doesn't exist.
#### `persistence.delMeta(docName: string, metaKey: string): Promise`
Delete a store meta value.
#### `persistence.getAllDocNames(docName: string): Promise<Array<string>>`
Retrieve the names of all stored documents.
#### `persistence.getAllDocStateVectors(docName: string): Promise<Array<{ name:string,clock:number,sv:Uint8Array}`
Retrieve the state vectors of all stored documents. You can use this to sync
two y-leveldb instances.
Note: The state vectors might be outdated if the associated document is not
yet flushed. So use with caution.
#### `persistence.flushDocument(docName: string): Promise` (dev only)
Internally y-leveldb stores incremental updates. You can merge all document
updates to a single entry. You probably never have to use this.
## License
y-leveldb is licensed under the [MIT License](./LICENSE).
<kevin.jahns@protonmail.com>

121
node_modules/y-leveldb/dist/src/y-leveldb.d.ts generated vendored Normal file
View File

@@ -0,0 +1,121 @@
/// <reference types="node" />
export const PREFERRED_TRIM_SIZE: 500;
export function writeUint32BigEndian(encoder: encoding.Encoder, num: number): void;
export function readUint32BigEndian(decoder: decoding.Decoder): number;
export const keyEncoding: {
buffer: boolean;
type: string;
encode: (arr: Array<string | number>) => Buffer;
decode: (buf: Uint8Array) => (string | number)[];
};
export function getLevelBulkData(db: any, opts: object): Promise<Array<any>>;
export function getLevelUpdates(db: any, docName: string, opts?: any): Promise<Array<Buffer>>;
export function getAllDocs(db: any, values: boolean, keys: boolean): Promise<Array<any>>;
export function getCurrentUpdateClock(db: any, docName: string): Promise<number>;
export class LeveldbPersistence {
/**
* @param {string} location
* @param {object} [opts]
* @param {any} [opts.level] Level-compatible adapter. E.g. leveldown, level-rem, level-indexeddb. Defaults to `level`
* @param {object} [opts.levelOptions] Options that are passed down to the level instance
*/
constructor(location: string, { level, levelOptions }?: {
level?: any;
levelOptions?: object;
} | undefined);
tr: Promise<any>;
/**
* Execute an transaction on a database. This will ensure that other processes are currently not writing.
*
* This is a private method and might change in the future.
*
* @todo only transact on the same room-name. Allow for concurrency of different rooms.
*
* @template T
*
* @param {function(any):Promise<T>} f A transaction that receives the db object
* @return {Promise<T>}
*/
_transact: <T>(f: (arg0: any) => Promise<T>) => Promise<T>;
/**
* @param {string} docName
*/
flushDocument(docName: string): Promise<void>;
/**
* @param {string} docName
* @return {Promise<Y.Doc>}
*/
getYDoc(docName: string): Promise<Y.Doc>;
/**
* @param {string} docName
* @return {Promise<Uint8Array>}
*/
getStateVector(docName: string): Promise<Uint8Array>;
/**
* @param {string} docName
* @param {Uint8Array} update
* @return {Promise<number>} Returns the clock of the stored update
*/
storeUpdate(docName: string, update: Uint8Array): Promise<number>;
/**
* @param {string} docName
* @param {Uint8Array} stateVector
*/
getDiff(docName: string, stateVector: Uint8Array): Promise<Uint8Array>;
/**
* @param {string} docName
* @return {Promise<void>}
*/
clearDocument(docName: string): Promise<void>;
/**
* @param {string} docName
* @param {string} metaKey
* @param {any} value
* @return {Promise<void>}
*/
setMeta(docName: string, metaKey: string, value: any): Promise<void>;
/**
* @param {string} docName
* @param {string} metaKey
* @return {Promise<any>}
*/
delMeta(docName: string, metaKey: string): Promise<any>;
/**
* @param {string} docName
* @param {string} metaKey
* @return {Promise<any>}
*/
getMeta(docName: string, metaKey: string): Promise<any>;
/**
* @return {Promise<Array<string>>}
*/
getAllDocNames(): Promise<Array<string>>;
/**
* @return {Promise<Array<{ name: string, sv: Uint8Array, clock: number }>>}
*/
getAllDocStateVectors(): Promise<Array<{
name: string;
sv: Uint8Array;
clock: number;
}>>;
/**
* @param {string} docName
* @return {Promise<Map<string, any>>}
*/
getMetas(docName: string): Promise<Map<string, any>>;
/**
* Close connection to a leveldb database and discard all state and bindings
*
* @return {Promise<void>}
*/
destroy(): Promise<void>;
/**
* Delete all data in database.
*/
clearAll(): Promise<any>;
}
import * as encoding from "lib0/encoding";
import * as decoding from "lib0/decoding";
import { Buffer } from "buffer";
import * as Y from "yjs";
//# sourceMappingURL=y-leveldb.d.ts.map

1
node_modules/y-leveldb/dist/src/y-leveldb.d.ts.map generated vendored Normal file
View File

@@ -0,0 +1 @@
{"version":3,"file":"y-leveldb.d.ts","sourceRoot":"","sources":["../../src/y-leveldb.js"],"names":[],"mappings":";AAUA,sCAAsC;AAoB/B,8CAHI,QAAQ,CAAC,OAAO,OAChB,MAAM,QAMhB;AAYM,6CAHI,QAAQ,CAAC,OAAO,GACf,MAAM,CAUjB;AAED;;;kBAIsB,KAAK,CAAC,MAAM,GAAC,MAAM,CAAC;kBAgBpB,UAAU;EAe/B;AA4CM,qCAJI,GAAG,QACH,MAAM,GACL,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAc7B;AAUK,oCALI,GAAG,WACH,MAAM,SACN,GAAG,GACF,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAMhC;AAUK,+BALI,GAAG,UACH,OAAO,QACP,OAAO,GACN,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAO7B;AAOK,0CAJI,GAAG,WACH,MAAM,GACL,OAAO,CAAC,MAAM,CAAC,CAQzB;AA8JF;IACE;;;;;OAKG;IACH,sBALW,MAAM;gBAEN,GAAG;uBACH,MAAM;mBAgChB;IA5BC,iBAA2B;IAC3B;;;;;;;;;;;OAWG;IACH,yBAHoB,GAAG,+BAiBtB;IAGH;;OAEG;IACH,uBAFW,MAAM,iBAQhB;IAED;;;OAGG;IACH,iBAHW,MAAM,GACL,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAgBzB;IAED;;;OAGG;IACH,wBAHW,MAAM,GACL,OAAO,CAAC,UAAU,CAAC,CAmB9B;IAED;;;;OAIG;IACH,qBAJW,MAAM,UACN,UAAU,GACT,OAAO,CAAC,MAAM,CAAC,CAI1B;IAED;;;OAGG;IACH,iBAHW,MAAM,eACN,UAAU,uBAKpB;IAED;;;OAGG;IACH,uBAHW,MAAM,GACL,OAAO,CAAC,IAAI,CAAC,CAOxB;IAED;;;;;OAKG;IACH,iBALW,MAAM,WACN,MAAM,SACN,GAAG,GACF,OAAO,CAAC,IAAI,CAAC,CAIxB;IAED;;;;OAIG;IACH,iBAJW,MAAM,WACN,MAAM,GACL,OAAO,CAAC,GAAG,CAAC,CAIvB;IAED;;;;OAIG;IACH,iBAJW,MAAM,WACN,MAAM,GACL,OAAO,CAAC,GAAG,CAAC,CAUvB;IAED;;OAEG;IACH,kBAFY,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAOjC;IAED;;OAEG;IACH,yBAFY,OAAO,CAAC,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,UAAU,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAU1E;IAED;;;OAGG;IACH,kBAHW,MAAM,GACL,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAcpC;IAED;;;;OAIG;IACH,WAFY,OAAO,CAAC,IAAI,CAAC,CAIxB;IAED;;OAEG;IACH,yBAEC;CACF"}

861
node_modules/y-leveldb/dist/test.cjs generated vendored Normal file
View File

@@ -0,0 +1,861 @@
'use strict';
var Y = require('yjs');
var encoding = require('lib0/dist/encoding.cjs');
var decoding = require('lib0/dist/decoding.cjs');
var binary = require('lib0/dist/binary.cjs');
var promise = require('lib0/dist/promise.cjs');
var buffer = require('lib0/dist/buffer.cjs');
var defaultLevel = require('level');
var buffer$1 = require('buffer');
var t = require('lib0/dist/testing.cjs');
var environment_js = require('lib0/dist/environment.cjs');
var log = require('lib0/dist/logging.cjs');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
function _interopNamespace(e) {
if (e && e.__esModule) return e;
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n["default"] = e;
return Object.freeze(n);
}
var Y__namespace = /*#__PURE__*/_interopNamespace(Y);
var encoding__namespace = /*#__PURE__*/_interopNamespace(encoding);
var decoding__namespace = /*#__PURE__*/_interopNamespace(decoding);
var binary__namespace = /*#__PURE__*/_interopNamespace(binary);
var promise__namespace = /*#__PURE__*/_interopNamespace(promise);
var buffer__namespace = /*#__PURE__*/_interopNamespace(buffer);
var defaultLevel__default = /*#__PURE__*/_interopDefaultLegacy(defaultLevel);
var t__namespace = /*#__PURE__*/_interopNamespace(t);
var log__namespace = /*#__PURE__*/_interopNamespace(log);
const PREFERRED_TRIM_SIZE = 500;
const YEncodingString = 0;
const YEncodingUint32 = 1;
const valueEncoding = {
buffer: true,
type: 'y-value',
encode: /** @param {any} data */ data => data,
decode: /** @param {any} data */ data => data
};
/**
* Write two bytes as an unsigned integer in big endian order.
* (most significant byte first)
*
* @function
* @param {encoding.Encoder} encoder
* @param {number} num The number that is to be encoded.
*/
const writeUint32BigEndian = (encoder, num) => {
for (let i = 3; i >= 0; i--) {
encoding__namespace.write(encoder, (num >>> (8 * i)) & binary__namespace.BITS8);
}
};
/**
* Read 4 bytes as unsigned integer in big endian order.
* (most significant byte first)
*
* @todo use lib0/decoding instead
*
* @function
* @param {decoding.Decoder} decoder
* @return {number} An unsigned integer.
*/
const readUint32BigEndian = decoder => {
const uint =
(decoder.arr[decoder.pos + 3] +
(decoder.arr[decoder.pos + 2] << 8) +
(decoder.arr[decoder.pos + 1] << 16) +
(decoder.arr[decoder.pos] << 24)) >>> 0;
decoder.pos += 4;
return uint
};
const keyEncoding = {
buffer: true,
type: 'y-keys',
/* istanbul ignore next */
encode: /** @param {Array<string|number>} arr */ arr => {
const encoder = encoding__namespace.createEncoder();
for (let i = 0; i < arr.length; i++) {
const v = arr[i];
if (typeof v === 'string') {
encoding__namespace.writeUint8(encoder, YEncodingString);
encoding__namespace.writeVarString(encoder, v);
} else /* istanbul ignore else */ if (typeof v === 'number') {
encoding__namespace.writeUint8(encoder, YEncodingUint32);
writeUint32BigEndian(encoder, v);
} else {
throw new Error('Unexpected key value')
}
}
return buffer$1.Buffer.from(encoding__namespace.toUint8Array(encoder))
},
decode: /** @param {Uint8Array} buf */ buf => {
const decoder = decoding__namespace.createDecoder(buf);
const key = [];
while (decoding__namespace.hasContent(decoder)) {
switch (decoding__namespace.readUint8(decoder)) {
case YEncodingString:
key.push(decoding__namespace.readVarString(decoder));
break
case YEncodingUint32:
key.push(readUint32BigEndian(decoder));
break
}
}
return key
}
};
/**
* level returns an error if a value is not found.
*
* This helper method for level returns `null` instead if the key is not found.
*
* @param {any} db
* @param {any} key
*/
const levelGet = async (db, key) => {
let res;
try {
res = await db.get(key);
} catch (err) {
/* istanbul ignore else */
if (err.notFound) {
return null
} else {
throw err
}
}
return res
};
/**
* Level expects a Buffer, but in Yjs we typically work with Uint8Arrays.
*
* Since Level thinks that these are two entirely different things,
* we transform the Uint8array to a Buffer before storing it.
*
* @param {any} db
* @param {any} key
* @param {Uint8Array} val
*/
const levelPut = async (db, key, val) => db.put(key, buffer$1.Buffer.from(val));
/**
* A "bulkier" implementation of level streams. Returns the result in one flush.
*
* @param {any} db
* @param {object} opts
* @return {Promise<Array<any>>}
*/
const getLevelBulkData = (db, opts) => promise__namespace.create((resolve, reject) => {
/**
* @type {Array<any>} result
*/
const result = [];
db.createReadStream(
opts
).on('data', /** @param {any} data */ data =>
result.push(data)
).on('end', () =>
resolve(result)
).on('error', reject);
});
/**
* Get all document updates for a specific document.
*
* @param {any} db
* @param {string} docName
* @param {any} [opts]
* @return {Promise<Array<Buffer>>}
*/
const getLevelUpdates = (db, docName, opts = { values: true, keys: false }) => getLevelBulkData(db, {
gte: createDocumentUpdateKey(docName, 0),
lt: createDocumentUpdateKey(docName, binary__namespace.BITS32),
...opts
});
/**
* Get all document updates for a specific document.
*
* @param {any} db
* @param {boolean} values
* @param {boolean} keys
* @return {Promise<Array<any>>}
*/
const getAllDocs = (db, values, keys) => getLevelBulkData(db, {
gte: ['v1_sv'],
lt: ['v1_sw'],
keys,
values
});
/**
* @param {any} db
* @param {string} docName
* @return {Promise<number>} Returns -1 if this document doesn't exist yet
*/
const getCurrentUpdateClock = (db, docName) => getLevelUpdates(db, docName, { keys: true, values: false, reverse: true, limit: 1 }).then(keys => {
if (keys.length === 0) {
return -1
} else {
return keys[0][3]
}
});
/**
* @param {any} db
* @param {Array<string|number>} gte Greater than or equal
* @param {Array<string|number>} lt lower than (not equal)
* @return {Promise<void>}
*/
const clearRange = async (db, gte, lt) => {
/* istanbul ignore else */
if (db.supports.clear) {
await db.clear({ gte, lt });
} else {
const keys = await getLevelBulkData(db, { values: false, keys: true, gte, lt });
const ops = keys.map(key => ({ type: 'del', key }));
await db.batch(ops);
}
};
/**
* @param {any} db
* @param {string} docName
* @param {number} from Greater than or equal
* @param {number} to lower than (not equal)
* @return {Promise<void>}
*/
const clearUpdatesRange = async (db, docName, from, to) => clearRange(db, createDocumentUpdateKey(docName, from), createDocumentUpdateKey(docName, to));
/**
* Create a unique key for a update message.
* We encode the result using `keyEncoding` which expects an array.
*
* @param {string} docName
* @param {number} clock must be unique
* @return {Array<string|number>}
*/
const createDocumentUpdateKey = (docName, clock) => ['v1', docName, 'update', clock];
/**
* @param {string} docName
* @param {string} metaKey
*/
const createDocumentMetaKey = (docName, metaKey) => ['v1', docName, 'meta', metaKey];
/**
* @param {string} docName
*/
const createDocumentMetaEndKey = (docName) => ['v1', docName, 'metb']; // simple trick
/**
* We have a separate state vector key so we can iterate efficiently over all documents
* @param {string} docName
*/
const createDocumentStateVectorKey = (docName) => ['v1_sv', docName];
/**
* @param {string} docName
*/
const createDocumentFirstKey = (docName) => ['v1', docName];
/**
* We use this key as the upper limit of all keys that can be written.
* Make sure that all document keys are smaller! Strings are encoded using varLength string encoding,
* so we need to make sure that this key has the biggest size!
*
* @param {string} docName
*/
const createDocumentLastKey = (docName) => ['v1', docName, 'zzzzzzz'];
// const emptyStateVector = (() => Y.encodeStateVector(new Y.Doc()))()
/**
* For now this is a helper method that creates a Y.Doc and then re-encodes a document update.
* In the future this will be handled by Yjs without creating a Y.Doc (constant memory consumption).
*
* @param {Array<Uint8Array>} updates
* @return {{update:Uint8Array, sv: Uint8Array}}
*/
const mergeUpdates = (updates) => {
const ydoc = new Y__namespace.Doc();
ydoc.transact(() => {
for (let i = 0; i < updates.length; i++) {
Y__namespace.applyUpdate(ydoc, updates[i]);
}
});
return { update: Y__namespace.encodeStateAsUpdate(ydoc), sv: Y__namespace.encodeStateVector(ydoc) }
};
/**
* @param {any} db
* @param {string} docName
* @param {Uint8Array} sv state vector
* @param {number} clock current clock of the document so we can determine when this statevector was created
*/
const writeStateVector = async (db, docName, sv, clock) => {
const encoder = encoding__namespace.createEncoder();
encoding__namespace.writeVarUint(encoder, clock);
encoding__namespace.writeVarUint8Array(encoder, sv);
await levelPut(db, createDocumentStateVectorKey(docName), encoding__namespace.toUint8Array(encoder));
};
/**
* @param {Uint8Array} buf
* @return {{ sv: Uint8Array, clock: number }}
*/
const decodeLeveldbStateVector = buf => {
const decoder = decoding__namespace.createDecoder(buf);
const clock = decoding__namespace.readVarUint(decoder);
const sv = decoding__namespace.readVarUint8Array(decoder);
return { sv, clock }
};
/**
* @param {any} db
* @param {string} docName
*/
const readStateVector$1 = async (db, docName) => {
const buf = await levelGet(db, createDocumentStateVectorKey(docName));
if (buf === null) {
// no state vector created yet or no document exists
return { sv: null, clock: -1 }
}
return decodeLeveldbStateVector(buf)
};
/**
* @param {any} db
* @param {string} docName
* @param {Uint8Array} stateAsUpdate
* @param {Uint8Array} stateVector
* @return {Promise<number>} returns the clock of the flushed doc
*/
const flushDocument = async (db, docName, stateAsUpdate, stateVector) => {
const clock = await storeUpdate(db, docName, stateAsUpdate);
await writeStateVector(db, docName, stateVector, clock);
await clearUpdatesRange(db, docName, 0, clock); // intentionally not waiting for the promise to resolve!
return clock
};
/**
* @param {any} db
* @param {string} docName
* @param {Uint8Array} update
* @return {Promise<number>} Returns the clock of the stored update
*/
const storeUpdate = async (db, docName, update) => {
const clock = await getCurrentUpdateClock(db, docName);
if (clock === -1) {
// make sure that a state vector is aways written, so we can search for available documents
const ydoc = new Y__namespace.Doc();
Y__namespace.applyUpdate(ydoc, update);
const sv = Y__namespace.encodeStateVector(ydoc);
await writeStateVector(db, docName, sv, 0);
}
await levelPut(db, createDocumentUpdateKey(docName, clock + 1), update);
return clock + 1
};
class LeveldbPersistence {
/**
* @param {string} location
* @param {object} [opts]
* @param {any} [opts.level] Level-compatible adapter. E.g. leveldown, level-rem, level-indexeddb. Defaults to `level`
* @param {object} [opts.levelOptions] Options that are passed down to the level instance
*/
constructor (location, /* istanbul ignore next */ { level = defaultLevel__default["default"], levelOptions = {} } = {}) {
const db = level(location, { ...levelOptions, valueEncoding, keyEncoding });
this.tr = promise__namespace.resolve();
/**
* Execute an transaction on a database. This will ensure that other processes are currently not writing.
*
* This is a private method and might change in the future.
*
* @todo only transact on the same room-name. Allow for concurrency of different rooms.
*
* @template T
*
* @param {function(any):Promise<T>} f A transaction that receives the db object
* @return {Promise<T>}
*/
this._transact = f => {
const currTr = this.tr;
this.tr = (async () => {
await currTr;
let res = /** @type {any} */ (null);
try {
res = await f(db);
} catch (err) {
/* istanbul ignore next */
console.warn('Error during y-leveldb transaction', err);
}
return res
})();
return this.tr
};
}
/**
* @param {string} docName
*/
flushDocument (docName) {
return this._transact(async db => {
const updates = await getLevelUpdates(db, docName);
const { update, sv } = mergeUpdates(updates);
await flushDocument(db, docName, update, sv);
})
}
/**
* @param {string} docName
* @return {Promise<Y.Doc>}
*/
getYDoc (docName) {
return this._transact(async db => {
const updates = await getLevelUpdates(db, docName);
const ydoc = new Y__namespace.Doc();
ydoc.transact(() => {
for (let i = 0; i < updates.length; i++) {
Y__namespace.applyUpdate(ydoc, updates[i]);
}
});
if (updates.length > PREFERRED_TRIM_SIZE) {
await flushDocument(db, docName, Y__namespace.encodeStateAsUpdate(ydoc), Y__namespace.encodeStateVector(ydoc));
}
return ydoc
})
}
/**
* @param {string} docName
* @return {Promise<Uint8Array>}
*/
getStateVector (docName) {
return this._transact(async db => {
const { clock, sv } = await readStateVector$1(db, docName);
let curClock = -1;
if (sv !== null) {
curClock = await getCurrentUpdateClock(db, docName);
}
if (sv !== null && clock === curClock) {
return sv
} else {
// current state vector is outdated
const updates = await getLevelUpdates(db, docName);
const { update, sv } = mergeUpdates(updates);
await flushDocument(db, docName, update, sv);
return sv
}
})
}
/**
* @param {string} docName
* @param {Uint8Array} update
* @return {Promise<number>} Returns the clock of the stored update
*/
storeUpdate (docName, update) {
return this._transact(db => storeUpdate(db, docName, update))
}
/**
* @param {string} docName
* @param {Uint8Array} stateVector
*/
async getDiff (docName, stateVector) {
const ydoc = await this.getYDoc(docName);
return Y__namespace.encodeStateAsUpdate(ydoc, stateVector)
}
/**
* @param {string} docName
* @return {Promise<void>}
*/
clearDocument (docName) {
return this._transact(async db => {
await db.del(createDocumentStateVectorKey(docName));
await clearRange(db, createDocumentFirstKey(docName), createDocumentLastKey(docName));
})
}
/**
* @param {string} docName
* @param {string} metaKey
* @param {any} value
* @return {Promise<void>}
*/
setMeta (docName, metaKey, value) {
return this._transact(db => levelPut(db, createDocumentMetaKey(docName, metaKey), buffer__namespace.encodeAny(value)))
}
/**
* @param {string} docName
* @param {string} metaKey
* @return {Promise<any>}
*/
delMeta (docName, metaKey) {
return this._transact(db => db.del(createDocumentMetaKey(docName, metaKey)))
}
/**
* @param {string} docName
* @param {string} metaKey
* @return {Promise<any>}
*/
getMeta (docName, metaKey) {
return this._transact(async db => {
const res = await levelGet(db, createDocumentMetaKey(docName, metaKey));
if (res === null) {
return// return void
}
return buffer__namespace.decodeAny(res)
})
}
/**
* @return {Promise<Array<string>>}
*/
getAllDocNames () {
return this._transact(async db => {
const docKeys = await getAllDocs(db, false, true);
return docKeys.map(key => key[1])
})
}
/**
* @return {Promise<Array<{ name: string, sv: Uint8Array, clock: number }>>}
*/
getAllDocStateVectors () {
return this._transact(async db => {
const docs = /** @type {any} */ (await getAllDocs(db, true, true));
return docs.map(doc => {
const { sv, clock } = decodeLeveldbStateVector(doc.value);
return { name: doc.key[1], sv, clock }
})
})
}
/**
* @param {string} docName
* @return {Promise<Map<string, any>>}
*/
getMetas (docName) {
return this._transact(async db => {
const data = await getLevelBulkData(db, {
gte: createDocumentMetaKey(docName, ''),
lt: createDocumentMetaEndKey(docName),
keys: true,
values: true
});
const metas = new Map();
data.forEach(v => { metas.set(v.key[3], buffer__namespace.decodeAny(v.value)); });
return metas
})
}
/**
* Close connection to a leveldb database and discard all state and bindings
*
* @return {Promise<void>}
*/
destroy () {
return this._transact(db => db.close())
}
/**
* Delete all data in database.
*/
clearAll () {
return this._transact(async db => db.clear())
}
}
// When changing this, also make sure to change the file in gitignore
const storageName = 'tmp-leveldb-storage';
/**
* Read state vector from Decoder and return as Map. This is a helper method that will be exported by Yjs directly.
*
* @param {decoding.Decoder} decoder
* @return {Map<number,number>} Maps `client` to the number next expected `clock` from that client.
*
* @function
*/
const readStateVector = decoder => {
const ss = new Map();
const ssLength = decoding__namespace.readVarUint(decoder);
for (let i = 0; i < ssLength; i++) {
const client = decoding__namespace.readVarUint(decoder);
const clock = decoding__namespace.readVarUint(decoder);
ss.set(client, clock);
}
return ss
};
/**
* Read decodedState and return State as Map.
*
* @param {Uint8Array} decodedState
* @return {Map<number,number>} Maps `client` to the number next expected `clock` from that client.
*
* @function
*/
const decodeStateVector = decodedState => readStateVector(decoding__namespace.createDecoder(decodedState));
/**
* Flushes all updates to ldb and delets items from updates array.
*
* @param {LeveldbPersistence} ldb
* @param {string} docName
* @param {Array<Uint8Array>} updates
*/
const flushUpdatesHelper = (ldb, docName, updates) =>
Promise.all(updates.splice(0).map(update => ldb.storeUpdate(docName, update)));
/**
* @param {t.TestCase} tc
*/
const testLeveldbUpdateStorage = async tc => {
const docName = tc.testName;
const ydoc1 = new Y__namespace.Doc();
ydoc1.clientID = 0; // so we can check the state vector
const leveldbPersistence = new LeveldbPersistence(storageName);
// clear all data, so we can check allData later
await leveldbPersistence._transact(async db => db.clear());
t__namespace.compareArrays([], await leveldbPersistence.getAllDocNames());
const updates = [];
ydoc1.on('update', update => {
updates.push(update);
});
ydoc1.getArray('arr').insert(0, [1]);
ydoc1.getArray('arr').insert(0, [2]);
await flushUpdatesHelper(leveldbPersistence, docName, updates);
const encodedSv = await leveldbPersistence.getStateVector(docName);
const sv = decodeStateVector(encodedSv);
t__namespace.assert(sv.size === 1);
t__namespace.assert(sv.get(0) === 2);
const ydoc2 = await leveldbPersistence.getYDoc(docName);
t__namespace.compareArrays(ydoc2.getArray('arr').toArray(), [2, 1]);
const allData = await leveldbPersistence._transact(async db => getLevelBulkData(db, { gte: ['v1'], lt: ['v2'] }));
t__namespace.assert(allData.length > 0, 'some data exists');
t__namespace.compareArrays([docName], await leveldbPersistence.getAllDocNames());
await leveldbPersistence.clearDocument(docName);
t__namespace.compareArrays([], await leveldbPersistence.getAllDocNames());
const allData2 = await leveldbPersistence._transact(async db => getLevelBulkData(db, { gte: ['v1'], lt: ['v2'] }));
console.log(allData2);
t__namespace.assert(allData2.length === 0, 'really deleted all data');
await leveldbPersistence.destroy();
};
/**
* @param {t.TestCase} tc
*/
const testEncodeManyUpdates = async tc => {
const N = PREFERRED_TRIM_SIZE * 7;
const docName = tc.testName;
const ydoc1 = new Y__namespace.Doc();
ydoc1.clientID = 0; // so we can check the state vector
const leveldbPersistence = new LeveldbPersistence(storageName);
await leveldbPersistence.clearDocument(docName);
const updates = [];
ydoc1.on('update', update => {
updates.push(update);
});
await flushUpdatesHelper(leveldbPersistence, docName, updates);
const keys = await leveldbPersistence._transact(db => getLevelUpdates(db, docName, { keys: true, values: false }));
for (let i = 0; i < keys.length; i++) {
t__namespace.assert(keys[i][3] === i);
}
const yarray = ydoc1.getArray('arr');
for (let i = 0; i < N; i++) {
yarray.insert(0, [i]);
}
await flushUpdatesHelper(leveldbPersistence, docName, updates);
const ydoc2 = await leveldbPersistence.getYDoc(docName);
t__namespace.assert(ydoc2.getArray('arr').length === N);
await leveldbPersistence.flushDocument(docName);
const mergedKeys = await leveldbPersistence._transact(db => getLevelUpdates(db, docName, { keys: true, values: false }));
t__namespace.assert(mergedKeys.length === 1);
// getYDoc still works after flush/merge
const ydoc3 = await leveldbPersistence.getYDoc(docName);
t__namespace.assert(ydoc3.getArray('arr').length === N);
// test if state vector is properly generated
t__namespace.compare(Y__namespace.encodeStateVector(ydoc1), await leveldbPersistence.getStateVector(docName));
// add new update so that sv needs to be updated
ydoc1.getArray('arr').insert(0, ['new']);
await flushUpdatesHelper(leveldbPersistence, docName, updates);
t__namespace.compare(Y__namespace.encodeStateVector(ydoc1), await leveldbPersistence.getStateVector(docName));
await leveldbPersistence.destroy();
};
/**
* @param {t.TestCase} tc
*/
const testDiff = async tc => {
const N = PREFERRED_TRIM_SIZE * 2; // primes are awesome - ensure that the document is at least flushed once
const docName = tc.testName;
const ydoc1 = new Y__namespace.Doc();
ydoc1.clientID = 0; // so we can check the state vector
const leveldbPersistence = new LeveldbPersistence(storageName);
await leveldbPersistence.clearDocument(docName);
const updates = [];
ydoc1.on('update', update => {
updates.push(update);
});
const yarray = ydoc1.getArray('arr');
// create N changes
for (let i = 0; i < N; i++) {
yarray.insert(0, [i]);
}
await flushUpdatesHelper(leveldbPersistence, docName, updates);
// create partially merged doc
const ydoc2 = await leveldbPersistence.getYDoc(docName);
// another N updates
for (let i = 0; i < N; i++) {
yarray.insert(0, [i]);
}
await flushUpdatesHelper(leveldbPersistence, docName, updates);
// apply diff to doc
const diffUpdate = await leveldbPersistence.getDiff(docName, Y__namespace.encodeStateVector(ydoc2));
Y__namespace.applyUpdate(ydoc2, diffUpdate);
t__namespace.assert(ydoc2.getArray('arr').length === ydoc1.getArray('arr').length);
t__namespace.assert(ydoc2.getArray('arr').length === N * 2);
await leveldbPersistence.destroy();
};
/**
* @param {t.TestCase} tc
*/
const testMetas = async tc => {
const docName = tc.testName;
const leveldbPersistence = new LeveldbPersistence(storageName);
await leveldbPersistence.clearDocument(docName);
await leveldbPersistence.setMeta(docName, 'a', 4);
await leveldbPersistence.setMeta(docName, 'a', 5);
await leveldbPersistence.setMeta(docName, 'b', 4);
const a = await leveldbPersistence.getMeta(docName, 'a');
const b = await leveldbPersistence.getMeta(docName, 'b');
t__namespace.assert(a === 5);
t__namespace.assert(b === 4);
const metas = await leveldbPersistence.getMetas(docName);
t__namespace.assert(metas.size === 2);
t__namespace.assert(metas.get('a') === 5);
t__namespace.assert(metas.get('b') === 4);
await leveldbPersistence.delMeta(docName, 'a');
const c = await leveldbPersistence.getMeta(docName, 'a');
t__namespace.assert(c === undefined);
await leveldbPersistence.clearDocument(docName);
const metasEmpty = await leveldbPersistence.getMetas(docName);
t__namespace.assert(metasEmpty.size === 0);
await leveldbPersistence.destroy();
};
/**
* @param {t.TestCase} tc
*/
const testDeleteEmptySv = async tc => {
const docName = tc.testName;
const leveldbPersistence = new LeveldbPersistence(storageName);
await leveldbPersistence.clearAll();
const ydoc = new Y__namespace.Doc();
ydoc.clientID = 0;
ydoc.getArray('arr').insert(0, [1]);
const singleUpdate = Y__namespace.encodeStateAsUpdate(ydoc);
t__namespace.compareArrays([], await leveldbPersistence.getAllDocNames());
await leveldbPersistence.storeUpdate(docName, singleUpdate);
t__namespace.compareArrays([docName], await leveldbPersistence.getAllDocNames());
const docSvs = await leveldbPersistence.getAllDocStateVectors();
t__namespace.assert(docSvs.length === 1);
t__namespace.compare([{ name: docName, clock: 0, sv: Y__namespace.encodeStateVector(ydoc) }], docSvs);
await leveldbPersistence.clearDocument(docName);
t__namespace.compareArrays([], await leveldbPersistence.getAllDocNames());
await leveldbPersistence.destroy();
};
const testMisc = async tc => {
const docName = tc.testName;
const leveldbPersistence = new LeveldbPersistence(storageName);
await leveldbPersistence.clearDocument(docName);
const sv = await leveldbPersistence.getStateVector('does not exist');
t__namespace.assert(sv.byteLength === 1);
await leveldbPersistence.destroy();
};
var leveldb = /*#__PURE__*/Object.freeze({
__proto__: null,
testLeveldbUpdateStorage: testLeveldbUpdateStorage,
testEncodeManyUpdates: testEncodeManyUpdates,
testDiff: testDiff,
testMetas: testMetas,
testDeleteEmptySv: testDeleteEmptySv,
testMisc: testMisc
});
if (environment_js.isBrowser) {
log__namespace.createVConsole(document.body);
}
t.runTests({
leveldb
}).then(success => {
/* istanbul ignore next */
if (environment_js.isNode) {
process.exit(success ? 0 : 1);
}
});
//# sourceMappingURL=test.cjs.map

1
node_modules/y-leveldb/dist/test.cjs.map generated vendored Normal file

File diff suppressed because one or more lines are too long

23086
node_modules/y-leveldb/dist/test.js generated vendored Normal file

File diff suppressed because one or more lines are too long

1
node_modules/y-leveldb/dist/test.js.map generated vendored Normal file

File diff suppressed because one or more lines are too long

2
node_modules/y-leveldb/dist/tests/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1,2 @@
export {};
//# sourceMappingURL=index.d.ts.map

1
node_modules/y-leveldb/dist/tests/index.d.ts.map generated vendored Normal file
View File

@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../tests/index.js"],"names":[],"mappings":""}

View File

@@ -0,0 +1,8 @@
export function testLeveldbUpdateStorage(tc: t.TestCase): Promise<void>;
export function testEncodeManyUpdates(tc: t.TestCase): Promise<void>;
export function testDiff(tc: t.TestCase): Promise<void>;
export function testMetas(tc: t.TestCase): Promise<void>;
export function testDeleteEmptySv(tc: t.TestCase): Promise<void>;
export function testMisc(tc: any): Promise<void>;
import * as t from "lib0/testing";
//# sourceMappingURL=y-leveldb.tests.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"y-leveldb.tests.d.ts","sourceRoot":"","sources":["../../tests/y-leveldb.tests.js"],"names":[],"mappings":"AAmDO,6CAFI,CAAC,CAAC,QAAQ,iBAyCpB;AAKM,0CAFI,CAAC,CAAC,QAAQ,iBAgDpB;AAKM,6BAFI,CAAC,CAAC,QAAQ,iBAuCpB;AAKM,8BAFI,CAAC,CAAC,QAAQ,iBA0BpB;AAKM,sCAFI,CAAC,CAAC,QAAQ,iBAsBpB;AAEM,iDASN"}

602
node_modules/y-leveldb/dist/y-leveldb.cjs generated vendored Normal file
View File

@@ -0,0 +1,602 @@
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var Y = require('yjs');
var encoding = require('lib0/dist/encoding.cjs');
var decoding = require('lib0/dist/decoding.cjs');
var binary = require('lib0/dist/binary.cjs');
var promise = require('lib0/dist/promise.cjs');
var buffer$1 = require('lib0/dist/buffer.cjs');
var defaultLevel = require('level');
var buffer = require('buffer');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
function _interopNamespace(e) {
if (e && e.__esModule) return e;
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n["default"] = e;
return Object.freeze(n);
}
var Y__namespace = /*#__PURE__*/_interopNamespace(Y);
var encoding__namespace = /*#__PURE__*/_interopNamespace(encoding);
var decoding__namespace = /*#__PURE__*/_interopNamespace(decoding);
var binary__namespace = /*#__PURE__*/_interopNamespace(binary);
var promise__namespace = /*#__PURE__*/_interopNamespace(promise);
var buffer__namespace = /*#__PURE__*/_interopNamespace(buffer$1);
var defaultLevel__default = /*#__PURE__*/_interopDefaultLegacy(defaultLevel);
const PREFERRED_TRIM_SIZE = 500;
const YEncodingString = 0;
const YEncodingUint32 = 1;
const valueEncoding = {
buffer: true,
type: 'y-value',
encode: /** @param {any} data */ data => data,
decode: /** @param {any} data */ data => data
};
/**
* Write two bytes as an unsigned integer in big endian order.
* (most significant byte first)
*
* @function
* @param {encoding.Encoder} encoder
* @param {number} num The number that is to be encoded.
*/
const writeUint32BigEndian = (encoder, num) => {
for (let i = 3; i >= 0; i--) {
encoding__namespace.write(encoder, (num >>> (8 * i)) & binary__namespace.BITS8);
}
};
/**
* Read 4 bytes as unsigned integer in big endian order.
* (most significant byte first)
*
* @todo use lib0/decoding instead
*
* @function
* @param {decoding.Decoder} decoder
* @return {number} An unsigned integer.
*/
const readUint32BigEndian = decoder => {
const uint =
(decoder.arr[decoder.pos + 3] +
(decoder.arr[decoder.pos + 2] << 8) +
(decoder.arr[decoder.pos + 1] << 16) +
(decoder.arr[decoder.pos] << 24)) >>> 0;
decoder.pos += 4;
return uint
};
const keyEncoding = {
buffer: true,
type: 'y-keys',
/* istanbul ignore next */
encode: /** @param {Array<string|number>} arr */ arr => {
const encoder = encoding__namespace.createEncoder();
for (let i = 0; i < arr.length; i++) {
const v = arr[i];
if (typeof v === 'string') {
encoding__namespace.writeUint8(encoder, YEncodingString);
encoding__namespace.writeVarString(encoder, v);
} else /* istanbul ignore else */ if (typeof v === 'number') {
encoding__namespace.writeUint8(encoder, YEncodingUint32);
writeUint32BigEndian(encoder, v);
} else {
throw new Error('Unexpected key value')
}
}
return buffer.Buffer.from(encoding__namespace.toUint8Array(encoder))
},
decode: /** @param {Uint8Array} buf */ buf => {
const decoder = decoding__namespace.createDecoder(buf);
const key = [];
while (decoding__namespace.hasContent(decoder)) {
switch (decoding__namespace.readUint8(decoder)) {
case YEncodingString:
key.push(decoding__namespace.readVarString(decoder));
break
case YEncodingUint32:
key.push(readUint32BigEndian(decoder));
break
}
}
return key
}
};
/**
* level returns an error if a value is not found.
*
* This helper method for level returns `null` instead if the key is not found.
*
* @param {any} db
* @param {any} key
*/
const levelGet = async (db, key) => {
let res;
try {
res = await db.get(key);
} catch (err) {
/* istanbul ignore else */
if (err.notFound) {
return null
} else {
throw err
}
}
return res
};
/**
* Level expects a Buffer, but in Yjs we typically work with Uint8Arrays.
*
* Since Level thinks that these are two entirely different things,
* we transform the Uint8array to a Buffer before storing it.
*
* @param {any} db
* @param {any} key
* @param {Uint8Array} val
*/
const levelPut = async (db, key, val) => db.put(key, buffer.Buffer.from(val));
/**
* A "bulkier" implementation of level streams. Returns the result in one flush.
*
* @param {any} db
* @param {object} opts
* @return {Promise<Array<any>>}
*/
const getLevelBulkData = (db, opts) => promise__namespace.create((resolve, reject) => {
/**
* @type {Array<any>} result
*/
const result = [];
db.createReadStream(
opts
).on('data', /** @param {any} data */ data =>
result.push(data)
).on('end', () =>
resolve(result)
).on('error', reject);
});
/**
* Get all document updates for a specific document.
*
* @param {any} db
* @param {string} docName
* @param {any} [opts]
* @return {Promise<Array<Buffer>>}
*/
const getLevelUpdates = (db, docName, opts = { values: true, keys: false }) => getLevelBulkData(db, {
gte: createDocumentUpdateKey(docName, 0),
lt: createDocumentUpdateKey(docName, binary__namespace.BITS32),
...opts
});
/**
* Get all document updates for a specific document.
*
* @param {any} db
* @param {boolean} values
* @param {boolean} keys
* @return {Promise<Array<any>>}
*/
const getAllDocs = (db, values, keys) => getLevelBulkData(db, {
gte: ['v1_sv'],
lt: ['v1_sw'],
keys,
values
});
/**
* @param {any} db
* @param {string} docName
* @return {Promise<number>} Returns -1 if this document doesn't exist yet
*/
const getCurrentUpdateClock = (db, docName) => getLevelUpdates(db, docName, { keys: true, values: false, reverse: true, limit: 1 }).then(keys => {
if (keys.length === 0) {
return -1
} else {
return keys[0][3]
}
});
/**
* @param {any} db
* @param {Array<string|number>} gte Greater than or equal
* @param {Array<string|number>} lt lower than (not equal)
* @return {Promise<void>}
*/
const clearRange = async (db, gte, lt) => {
/* istanbul ignore else */
if (db.supports.clear) {
await db.clear({ gte, lt });
} else {
const keys = await getLevelBulkData(db, { values: false, keys: true, gte, lt });
const ops = keys.map(key => ({ type: 'del', key }));
await db.batch(ops);
}
};
/**
* @param {any} db
* @param {string} docName
* @param {number} from Greater than or equal
* @param {number} to lower than (not equal)
* @return {Promise<void>}
*/
const clearUpdatesRange = async (db, docName, from, to) => clearRange(db, createDocumentUpdateKey(docName, from), createDocumentUpdateKey(docName, to));
/**
* Create a unique key for a update message.
* We encode the result using `keyEncoding` which expects an array.
*
* @param {string} docName
* @param {number} clock must be unique
* @return {Array<string|number>}
*/
const createDocumentUpdateKey = (docName, clock) => ['v1', docName, 'update', clock];
/**
* @param {string} docName
* @param {string} metaKey
*/
const createDocumentMetaKey = (docName, metaKey) => ['v1', docName, 'meta', metaKey];
/**
* @param {string} docName
*/
const createDocumentMetaEndKey = (docName) => ['v1', docName, 'metb']; // simple trick
/**
* We have a separate state vector key so we can iterate efficiently over all documents
* @param {string} docName
*/
const createDocumentStateVectorKey = (docName) => ['v1_sv', docName];
/**
* @param {string} docName
*/
const createDocumentFirstKey = (docName) => ['v1', docName];
/**
* We use this key as the upper limit of all keys that can be written.
* Make sure that all document keys are smaller! Strings are encoded using varLength string encoding,
* so we need to make sure that this key has the biggest size!
*
* @param {string} docName
*/
const createDocumentLastKey = (docName) => ['v1', docName, 'zzzzzzz'];
// const emptyStateVector = (() => Y.encodeStateVector(new Y.Doc()))()
/**
* For now this is a helper method that creates a Y.Doc and then re-encodes a document update.
* In the future this will be handled by Yjs without creating a Y.Doc (constant memory consumption).
*
* @param {Array<Uint8Array>} updates
* @return {{update:Uint8Array, sv: Uint8Array}}
*/
const mergeUpdates = (updates) => {
const ydoc = new Y__namespace.Doc();
ydoc.transact(() => {
for (let i = 0; i < updates.length; i++) {
Y__namespace.applyUpdate(ydoc, updates[i]);
}
});
return { update: Y__namespace.encodeStateAsUpdate(ydoc), sv: Y__namespace.encodeStateVector(ydoc) }
};
/**
* @param {any} db
* @param {string} docName
* @param {Uint8Array} sv state vector
* @param {number} clock current clock of the document so we can determine when this statevector was created
*/
const writeStateVector = async (db, docName, sv, clock) => {
const encoder = encoding__namespace.createEncoder();
encoding__namespace.writeVarUint(encoder, clock);
encoding__namespace.writeVarUint8Array(encoder, sv);
await levelPut(db, createDocumentStateVectorKey(docName), encoding__namespace.toUint8Array(encoder));
};
/**
* @param {Uint8Array} buf
* @return {{ sv: Uint8Array, clock: number }}
*/
const decodeLeveldbStateVector = buf => {
const decoder = decoding__namespace.createDecoder(buf);
const clock = decoding__namespace.readVarUint(decoder);
const sv = decoding__namespace.readVarUint8Array(decoder);
return { sv, clock }
};
/**
* @param {any} db
* @param {string} docName
*/
const readStateVector = async (db, docName) => {
const buf = await levelGet(db, createDocumentStateVectorKey(docName));
if (buf === null) {
// no state vector created yet or no document exists
return { sv: null, clock: -1 }
}
return decodeLeveldbStateVector(buf)
};
/**
* @param {any} db
* @param {string} docName
* @param {Uint8Array} stateAsUpdate
* @param {Uint8Array} stateVector
* @return {Promise<number>} returns the clock of the flushed doc
*/
const flushDocument = async (db, docName, stateAsUpdate, stateVector) => {
const clock = await storeUpdate(db, docName, stateAsUpdate);
await writeStateVector(db, docName, stateVector, clock);
await clearUpdatesRange(db, docName, 0, clock); // intentionally not waiting for the promise to resolve!
return clock
};
/**
* @param {any} db
* @param {string} docName
* @param {Uint8Array} update
* @return {Promise<number>} Returns the clock of the stored update
*/
const storeUpdate = async (db, docName, update) => {
const clock = await getCurrentUpdateClock(db, docName);
if (clock === -1) {
// make sure that a state vector is aways written, so we can search for available documents
const ydoc = new Y__namespace.Doc();
Y__namespace.applyUpdate(ydoc, update);
const sv = Y__namespace.encodeStateVector(ydoc);
await writeStateVector(db, docName, sv, 0);
}
await levelPut(db, createDocumentUpdateKey(docName, clock + 1), update);
return clock + 1
};
class LeveldbPersistence {
/**
* @param {string} location
* @param {object} [opts]
* @param {any} [opts.level] Level-compatible adapter. E.g. leveldown, level-rem, level-indexeddb. Defaults to `level`
* @param {object} [opts.levelOptions] Options that are passed down to the level instance
*/
constructor (location, /* istanbul ignore next */ { level = defaultLevel__default["default"], levelOptions = {} } = {}) {
const db = level(location, { ...levelOptions, valueEncoding, keyEncoding });
this.tr = promise__namespace.resolve();
/**
* Execute an transaction on a database. This will ensure that other processes are currently not writing.
*
* This is a private method and might change in the future.
*
* @todo only transact on the same room-name. Allow for concurrency of different rooms.
*
* @template T
*
* @param {function(any):Promise<T>} f A transaction that receives the db object
* @return {Promise<T>}
*/
this._transact = f => {
const currTr = this.tr;
this.tr = (async () => {
await currTr;
let res = /** @type {any} */ (null);
try {
res = await f(db);
} catch (err) {
/* istanbul ignore next */
console.warn('Error during y-leveldb transaction', err);
}
return res
})();
return this.tr
};
}
/**
* @param {string} docName
*/
flushDocument (docName) {
return this._transact(async db => {
const updates = await getLevelUpdates(db, docName);
const { update, sv } = mergeUpdates(updates);
await flushDocument(db, docName, update, sv);
})
}
/**
* @param {string} docName
* @return {Promise<Y.Doc>}
*/
getYDoc (docName) {
return this._transact(async db => {
const updates = await getLevelUpdates(db, docName);
const ydoc = new Y__namespace.Doc();
ydoc.transact(() => {
for (let i = 0; i < updates.length; i++) {
Y__namespace.applyUpdate(ydoc, updates[i]);
}
});
if (updates.length > PREFERRED_TRIM_SIZE) {
await flushDocument(db, docName, Y__namespace.encodeStateAsUpdate(ydoc), Y__namespace.encodeStateVector(ydoc));
}
return ydoc
})
}
/**
* @param {string} docName
* @return {Promise<Uint8Array>}
*/
getStateVector (docName) {
return this._transact(async db => {
const { clock, sv } = await readStateVector(db, docName);
let curClock = -1;
if (sv !== null) {
curClock = await getCurrentUpdateClock(db, docName);
}
if (sv !== null && clock === curClock) {
return sv
} else {
// current state vector is outdated
const updates = await getLevelUpdates(db, docName);
const { update, sv } = mergeUpdates(updates);
await flushDocument(db, docName, update, sv);
return sv
}
})
}
/**
* @param {string} docName
* @param {Uint8Array} update
* @return {Promise<number>} Returns the clock of the stored update
*/
storeUpdate (docName, update) {
return this._transact(db => storeUpdate(db, docName, update))
}
/**
* @param {string} docName
* @param {Uint8Array} stateVector
*/
async getDiff (docName, stateVector) {
const ydoc = await this.getYDoc(docName);
return Y__namespace.encodeStateAsUpdate(ydoc, stateVector)
}
/**
* @param {string} docName
* @return {Promise<void>}
*/
clearDocument (docName) {
return this._transact(async db => {
await db.del(createDocumentStateVectorKey(docName));
await clearRange(db, createDocumentFirstKey(docName), createDocumentLastKey(docName));
})
}
/**
* @param {string} docName
* @param {string} metaKey
* @param {any} value
* @return {Promise<void>}
*/
setMeta (docName, metaKey, value) {
return this._transact(db => levelPut(db, createDocumentMetaKey(docName, metaKey), buffer__namespace.encodeAny(value)))
}
/**
* @param {string} docName
* @param {string} metaKey
* @return {Promise<any>}
*/
delMeta (docName, metaKey) {
return this._transact(db => db.del(createDocumentMetaKey(docName, metaKey)))
}
/**
* @param {string} docName
* @param {string} metaKey
* @return {Promise<any>}
*/
getMeta (docName, metaKey) {
return this._transact(async db => {
const res = await levelGet(db, createDocumentMetaKey(docName, metaKey));
if (res === null) {
return// return void
}
return buffer__namespace.decodeAny(res)
})
}
/**
* @return {Promise<Array<string>>}
*/
getAllDocNames () {
return this._transact(async db => {
const docKeys = await getAllDocs(db, false, true);
return docKeys.map(key => key[1])
})
}
/**
* @return {Promise<Array<{ name: string, sv: Uint8Array, clock: number }>>}
*/
getAllDocStateVectors () {
return this._transact(async db => {
const docs = /** @type {any} */ (await getAllDocs(db, true, true));
return docs.map(doc => {
const { sv, clock } = decodeLeveldbStateVector(doc.value);
return { name: doc.key[1], sv, clock }
})
})
}
/**
* @param {string} docName
* @return {Promise<Map<string, any>>}
*/
getMetas (docName) {
return this._transact(async db => {
const data = await getLevelBulkData(db, {
gte: createDocumentMetaKey(docName, ''),
lt: createDocumentMetaEndKey(docName),
keys: true,
values: true
});
const metas = new Map();
data.forEach(v => { metas.set(v.key[3], buffer__namespace.decodeAny(v.value)); });
return metas
})
}
/**
* Close connection to a leveldb database and discard all state and bindings
*
* @return {Promise<void>}
*/
destroy () {
return this._transact(db => db.close())
}
/**
* Delete all data in database.
*/
clearAll () {
return this._transact(async db => db.clear())
}
}
exports.LeveldbPersistence = LeveldbPersistence;
exports.PREFERRED_TRIM_SIZE = PREFERRED_TRIM_SIZE;
exports.getAllDocs = getAllDocs;
exports.getCurrentUpdateClock = getCurrentUpdateClock;
exports.getLevelBulkData = getLevelBulkData;
exports.getLevelUpdates = getLevelUpdates;
exports.keyEncoding = keyEncoding;
exports.readUint32BigEndian = readUint32BigEndian;
exports.writeUint32BigEndian = writeUint32BigEndian;
//# sourceMappingURL=y-leveldb.cjs.map

1
node_modules/y-leveldb/dist/y-leveldb.cjs.map generated vendored Normal file

File diff suppressed because one or more lines are too long

81
node_modules/y-leveldb/package.json generated vendored Normal file
View File

@@ -0,0 +1,81 @@
{
"name": "y-leveldb",
"version": "0.1.2",
"description": "LevelDb database adapter for Yjs",
"type": "module",
"main": "./dist/y-leveldb.cjs",
"module": "./src/y-leveldb.js",
"types": "./dist/src/y-leveldb.d.ts",
"sideEffects": false,
"funding": {
"type": "GitHub Sponsors ❤",
"url": "https://github.com/sponsors/dmonad"
},
"scripts": {
"clean": "rm -rf dist",
"test": "rollup -c && nyc --check-coverage --lines 100 --branches 100 --functions 100 --statements 100 node ./dist/test.cjs",
"dist": "rollup -c",
"lint": "markdownlint README.md && standard && tsc",
"preversion": "npm run lint && npm run test && npm run clean && npm run dist && tsc && test -e dist/src/y-leveldb.d.ts && test -e dist/y-leveldb.cjs",
"debug": "concurrently 'rollup -wc' 'http-server -o .'"
},
"files": [
"dist/*",
"src/*"
],
"standard": {
"ignore": [
"/dist",
"/node_modules",
"/docs"
]
},
"exports": {
"./package.json": "./package.json",
".": {
"import": "./src/y-leveldb.js",
"require": "./dist/y-leveldb.cjs"
}
},
"repository": {
"type": "git",
"url": "git+https://github.com/yjs/y-leveldb.git"
},
"keywords": [
"Yjs",
"CRDT",
"offline",
"shared editing",
"collaboration",
"concurrency"
],
"author": "Kevin Jahns <kevin.jahns@protonmail.com>",
"license": "MIT",
"bugs": {
"url": "https://github.com/yjs/y-leveldb/issues"
},
"homepage": "https://yjs.dev",
"dependencies": {
"level": "^6.0.1",
"lib0": "^0.2.31"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^13.0.0",
"@rollup/plugin-node-resolve": "^8.1.0",
"concurrently": "^3.6.1",
"http-server": "^0.12.3",
"jsdoc": "^3.6.3",
"level-mem": "^5.0.1",
"markdownlint-cli": "^0.23.2",
"nyc": "^15.1.0",
"rollup": "^2.20.0",
"rollup-plugin-node-polyfills": "^0.2.1",
"standard": "^14.3.4",
"typescript": "^3.9.6",
"y-protocols": "^1.0.0",
"yjs": "^13.2.0"
},
"peerDependencies": {
"yjs": "^13.0.0"
}
}

560
node_modules/y-leveldb/src/y-leveldb.js generated vendored Normal file
View File

@@ -0,0 +1,560 @@
import * as Y from 'yjs'
import * as encoding from 'lib0/encoding.js'
import * as decoding from 'lib0/decoding.js'
import * as binary from 'lib0/binary.js'
import * as promise from 'lib0/promise.js'
import * as buffer from 'lib0/buffer.js'
// @ts-ignore
import defaultLevel from 'level'
import { Buffer } from 'buffer'
export const PREFERRED_TRIM_SIZE = 500
const YEncodingString = 0
const YEncodingUint32 = 1
const valueEncoding = {
buffer: true,
type: 'y-value',
encode: /** @param {any} data */ data => data,
decode: /** @param {any} data */ data => data
}
/**
* Write two bytes as an unsigned integer in big endian order.
* (most significant byte first)
*
* @function
* @param {encoding.Encoder} encoder
* @param {number} num The number that is to be encoded.
*/
export const writeUint32BigEndian = (encoder, num) => {
for (let i = 3; i >= 0; i--) {
encoding.write(encoder, (num >>> (8 * i)) & binary.BITS8)
}
}
/**
* Read 4 bytes as unsigned integer in big endian order.
* (most significant byte first)
*
* @todo use lib0/decoding instead
*
* @function
* @param {decoding.Decoder} decoder
* @return {number} An unsigned integer.
*/
export const readUint32BigEndian = decoder => {
const uint =
(decoder.arr[decoder.pos + 3] +
(decoder.arr[decoder.pos + 2] << 8) +
(decoder.arr[decoder.pos + 1] << 16) +
(decoder.arr[decoder.pos] << 24)) >>> 0
decoder.pos += 4
return uint
}
export const keyEncoding = {
buffer: true,
type: 'y-keys',
/* istanbul ignore next */
encode: /** @param {Array<string|number>} arr */ arr => {
const encoder = encoding.createEncoder()
for (let i = 0; i < arr.length; i++) {
const v = arr[i]
if (typeof v === 'string') {
encoding.writeUint8(encoder, YEncodingString)
encoding.writeVarString(encoder, v)
} else /* istanbul ignore else */ if (typeof v === 'number') {
encoding.writeUint8(encoder, YEncodingUint32)
writeUint32BigEndian(encoder, v)
} else {
throw new Error('Unexpected key value')
}
}
return Buffer.from(encoding.toUint8Array(encoder))
},
decode: /** @param {Uint8Array} buf */ buf => {
const decoder = decoding.createDecoder(buf)
const key = []
while (decoding.hasContent(decoder)) {
switch (decoding.readUint8(decoder)) {
case YEncodingString:
key.push(decoding.readVarString(decoder))
break
case YEncodingUint32:
key.push(readUint32BigEndian(decoder))
break
}
}
return key
}
}
/**
* level returns an error if a value is not found.
*
* This helper method for level returns `null` instead if the key is not found.
*
* @param {any} db
* @param {any} key
*/
const levelGet = async (db, key) => {
let res
try {
res = await db.get(key)
} catch (err) {
/* istanbul ignore else */
if (err.notFound) {
return null
} else {
throw err
}
}
return res
}
/**
* Level expects a Buffer, but in Yjs we typically work with Uint8Arrays.
*
* Since Level thinks that these are two entirely different things,
* we transform the Uint8array to a Buffer before storing it.
*
* @param {any} db
* @param {any} key
* @param {Uint8Array} val
*/
const levelPut = async (db, key, val) => db.put(key, Buffer.from(val))
/**
* A "bulkier" implementation of level streams. Returns the result in one flush.
*
* @param {any} db
* @param {object} opts
* @return {Promise<Array<any>>}
*/
export const getLevelBulkData = (db, opts) => promise.create((resolve, reject) => {
/**
* @type {Array<any>} result
*/
const result = []
db.createReadStream(
opts
).on('data', /** @param {any} data */ data =>
result.push(data)
).on('end', () =>
resolve(result)
).on('error', reject)
})
/**
* Get all document updates for a specific document.
*
* @param {any} db
* @param {string} docName
* @param {any} [opts]
* @return {Promise<Array<Buffer>>}
*/
export const getLevelUpdates = (db, docName, opts = { values: true, keys: false }) => getLevelBulkData(db, {
gte: createDocumentUpdateKey(docName, 0),
lt: createDocumentUpdateKey(docName, binary.BITS32),
...opts
})
/**
* Get all document updates for a specific document.
*
* @param {any} db
* @param {boolean} values
* @param {boolean} keys
* @return {Promise<Array<any>>}
*/
export const getAllDocs = (db, values, keys) => getLevelBulkData(db, {
gte: ['v1_sv'],
lt: ['v1_sw'],
keys,
values
})
/**
* @param {any} db
* @param {string} docName
* @return {Promise<number>} Returns -1 if this document doesn't exist yet
*/
export const getCurrentUpdateClock = (db, docName) => getLevelUpdates(db, docName, { keys: true, values: false, reverse: true, limit: 1 }).then(keys => {
if (keys.length === 0) {
return -1
} else {
return keys[0][3]
}
})
/**
* @param {any} db
* @param {Array<string|number>} gte Greater than or equal
* @param {Array<string|number>} lt lower than (not equal)
* @return {Promise<void>}
*/
const clearRange = async (db, gte, lt) => {
/* istanbul ignore else */
if (db.supports.clear) {
await db.clear({ gte, lt })
} else {
const keys = await getLevelBulkData(db, { values: false, keys: true, gte, lt })
const ops = keys.map(key => ({ type: 'del', key }))
await db.batch(ops)
}
}
/**
* @param {any} db
* @param {string} docName
* @param {number} from Greater than or equal
* @param {number} to lower than (not equal)
* @return {Promise<void>}
*/
const clearUpdatesRange = async (db, docName, from, to) => clearRange(db, createDocumentUpdateKey(docName, from), createDocumentUpdateKey(docName, to))
/**
* Create a unique key for a update message.
* We encode the result using `keyEncoding` which expects an array.
*
* @param {string} docName
* @param {number} clock must be unique
* @return {Array<string|number>}
*/
const createDocumentUpdateKey = (docName, clock) => ['v1', docName, 'update', clock]
/**
* @param {string} docName
* @param {string} metaKey
*/
const createDocumentMetaKey = (docName, metaKey) => ['v1', docName, 'meta', metaKey]
/**
* @param {string} docName
*/
const createDocumentMetaEndKey = (docName) => ['v1', docName, 'metb'] // simple trick
/**
* We have a separate state vector key so we can iterate efficiently over all documents
* @param {string} docName
*/
const createDocumentStateVectorKey = (docName) => ['v1_sv', docName]
/**
* @param {string} docName
*/
const createDocumentFirstKey = (docName) => ['v1', docName]
/**
* We use this key as the upper limit of all keys that can be written.
* Make sure that all document keys are smaller! Strings are encoded using varLength string encoding,
* so we need to make sure that this key has the biggest size!
*
* @param {string} docName
*/
const createDocumentLastKey = (docName) => ['v1', docName, 'zzzzzzz']
// const emptyStateVector = (() => Y.encodeStateVector(new Y.Doc()))()
/**
* For now this is a helper method that creates a Y.Doc and then re-encodes a document update.
* In the future this will be handled by Yjs without creating a Y.Doc (constant memory consumption).
*
* @param {Array<Uint8Array>} updates
* @return {{update:Uint8Array, sv: Uint8Array}}
*/
const mergeUpdates = (updates) => {
const ydoc = new Y.Doc()
ydoc.transact(() => {
for (let i = 0; i < updates.length; i++) {
Y.applyUpdate(ydoc, updates[i])
}
})
return { update: Y.encodeStateAsUpdate(ydoc), sv: Y.encodeStateVector(ydoc) }
}
/**
* @param {any} db
* @param {string} docName
* @param {Uint8Array} sv state vector
* @param {number} clock current clock of the document so we can determine when this statevector was created
*/
const writeStateVector = async (db, docName, sv, clock) => {
const encoder = encoding.createEncoder()
encoding.writeVarUint(encoder, clock)
encoding.writeVarUint8Array(encoder, sv)
await levelPut(db, createDocumentStateVectorKey(docName), encoding.toUint8Array(encoder))
}
/**
* @param {Uint8Array} buf
* @return {{ sv: Uint8Array, clock: number }}
*/
const decodeLeveldbStateVector = buf => {
const decoder = decoding.createDecoder(buf)
const clock = decoding.readVarUint(decoder)
const sv = decoding.readVarUint8Array(decoder)
return { sv, clock }
}
/**
* @param {any} db
* @param {string} docName
*/
const readStateVector = async (db, docName) => {
const buf = await levelGet(db, createDocumentStateVectorKey(docName))
if (buf === null) {
// no state vector created yet or no document exists
return { sv: null, clock: -1 }
}
return decodeLeveldbStateVector(buf)
}
/**
* @param {any} db
* @param {string} docName
* @param {Uint8Array} stateAsUpdate
* @param {Uint8Array} stateVector
* @return {Promise<number>} returns the clock of the flushed doc
*/
const flushDocument = async (db, docName, stateAsUpdate, stateVector) => {
const clock = await storeUpdate(db, docName, stateAsUpdate)
await writeStateVector(db, docName, stateVector, clock)
await clearUpdatesRange(db, docName, 0, clock) // intentionally not waiting for the promise to resolve!
return clock
}
/**
* @param {any} db
* @param {string} docName
* @param {Uint8Array} update
* @return {Promise<number>} Returns the clock of the stored update
*/
const storeUpdate = async (db, docName, update) => {
const clock = await getCurrentUpdateClock(db, docName)
if (clock === -1) {
// make sure that a state vector is aways written, so we can search for available documents
const ydoc = new Y.Doc()
Y.applyUpdate(ydoc, update)
const sv = Y.encodeStateVector(ydoc)
await writeStateVector(db, docName, sv, 0)
}
await levelPut(db, createDocumentUpdateKey(docName, clock + 1), update)
return clock + 1
}
export class LeveldbPersistence {
/**
* @param {string} location
* @param {object} [opts]
* @param {any} [opts.level] Level-compatible adapter. E.g. leveldown, level-rem, level-indexeddb. Defaults to `level`
* @param {object} [opts.levelOptions] Options that are passed down to the level instance
*/
constructor (location, /* istanbul ignore next */ { level = defaultLevel, levelOptions = {} } = {}) {
const db = level(location, { ...levelOptions, valueEncoding, keyEncoding })
this.tr = promise.resolve()
/**
* Execute an transaction on a database. This will ensure that other processes are currently not writing.
*
* This is a private method and might change in the future.
*
* @todo only transact on the same room-name. Allow for concurrency of different rooms.
*
* @template T
*
* @param {function(any):Promise<T>} f A transaction that receives the db object
* @return {Promise<T>}
*/
this._transact = f => {
const currTr = this.tr
this.tr = (async () => {
await currTr
let res = /** @type {any} */ (null)
try {
res = await f(db)
} catch (err) {
/* istanbul ignore next */
console.warn('Error during y-leveldb transaction', err)
}
return res
})()
return this.tr
}
}
/**
* @param {string} docName
*/
flushDocument (docName) {
return this._transact(async db => {
const updates = await getLevelUpdates(db, docName)
const { update, sv } = mergeUpdates(updates)
await flushDocument(db, docName, update, sv)
})
}
/**
* @param {string} docName
* @return {Promise<Y.Doc>}
*/
getYDoc (docName) {
return this._transact(async db => {
const updates = await getLevelUpdates(db, docName)
const ydoc = new Y.Doc()
ydoc.transact(() => {
for (let i = 0; i < updates.length; i++) {
Y.applyUpdate(ydoc, updates[i])
}
})
if (updates.length > PREFERRED_TRIM_SIZE) {
await flushDocument(db, docName, Y.encodeStateAsUpdate(ydoc), Y.encodeStateVector(ydoc))
}
return ydoc
})
}
/**
* @param {string} docName
* @return {Promise<Uint8Array>}
*/
getStateVector (docName) {
return this._transact(async db => {
const { clock, sv } = await readStateVector(db, docName)
let curClock = -1
if (sv !== null) {
curClock = await getCurrentUpdateClock(db, docName)
}
if (sv !== null && clock === curClock) {
return sv
} else {
// current state vector is outdated
const updates = await getLevelUpdates(db, docName)
const { update, sv } = mergeUpdates(updates)
await flushDocument(db, docName, update, sv)
return sv
}
})
}
/**
* @param {string} docName
* @param {Uint8Array} update
* @return {Promise<number>} Returns the clock of the stored update
*/
storeUpdate (docName, update) {
return this._transact(db => storeUpdate(db, docName, update))
}
/**
* @param {string} docName
* @param {Uint8Array} stateVector
*/
async getDiff (docName, stateVector) {
const ydoc = await this.getYDoc(docName)
return Y.encodeStateAsUpdate(ydoc, stateVector)
}
/**
* @param {string} docName
* @return {Promise<void>}
*/
clearDocument (docName) {
return this._transact(async db => {
await db.del(createDocumentStateVectorKey(docName))
await clearRange(db, createDocumentFirstKey(docName), createDocumentLastKey(docName))
})
}
/**
* @param {string} docName
* @param {string} metaKey
* @param {any} value
* @return {Promise<void>}
*/
setMeta (docName, metaKey, value) {
return this._transact(db => levelPut(db, createDocumentMetaKey(docName, metaKey), buffer.encodeAny(value)))
}
/**
* @param {string} docName
* @param {string} metaKey
* @return {Promise<any>}
*/
delMeta (docName, metaKey) {
return this._transact(db => db.del(createDocumentMetaKey(docName, metaKey)))
}
/**
* @param {string} docName
* @param {string} metaKey
* @return {Promise<any>}
*/
getMeta (docName, metaKey) {
return this._transact(async db => {
const res = await levelGet(db, createDocumentMetaKey(docName, metaKey))
if (res === null) {
return// return void
}
return buffer.decodeAny(res)
})
}
/**
* @return {Promise<Array<string>>}
*/
getAllDocNames () {
return this._transact(async db => {
const docKeys = await getAllDocs(db, false, true)
return docKeys.map(key => key[1])
})
}
/**
* @return {Promise<Array<{ name: string, sv: Uint8Array, clock: number }>>}
*/
getAllDocStateVectors () {
return this._transact(async db => {
const docs = /** @type {any} */ (await getAllDocs(db, true, true))
return docs.map(doc => {
const { sv, clock } = decodeLeveldbStateVector(doc.value)
return { name: doc.key[1], sv, clock }
})
})
}
/**
* @param {string} docName
* @return {Promise<Map<string, any>>}
*/
getMetas (docName) {
return this._transact(async db => {
const data = await getLevelBulkData(db, {
gte: createDocumentMetaKey(docName, ''),
lt: createDocumentMetaEndKey(docName),
keys: true,
values: true
})
const metas = new Map()
data.forEach(v => { metas.set(v.key[3], buffer.decodeAny(v.value)) })
return metas
})
}
/**
* Close connection to a leveldb database and discard all state and bindings
*
* @return {Promise<void>}
*/
destroy () {
return this._transact(db => db.close())
}
/**
* Delete all data in database.
*/
clearAll () {
return this._transact(async db => db.clear())
}
}