Full Code of yjs/y-indexeddb for AI

master ff468b5e9cb3 cached
12 files
16.5 KB
5.0k tokens
8 symbols
1 requests
Download .txt
Repository: yjs/y-indexeddb
Branch: master
Commit: ff468b5e9cb3
Files: 12
Total size: 16.5 KB

Directory structure:
gitextract_7i4vyn17/

├── .github/
│   └── workflows/
│       └── node.js.yml
├── .gitignore
├── .markdownlint.json
├── LICENSE
├── README.md
├── index.html
├── package.json
├── rollup.config.js
├── src/
│   └── y-indexeddb.js
├── tests/
│   ├── index.js
│   └── y-indexeddb.tests.js
└── tsconfig.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/node.js.yml
================================================
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions

name: Node.js CI

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

jobs:
  build:

    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [16.x, 18.x]

    steps:
    - uses: actions/checkout@v2
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v1
      with:
        node-version: ${{ matrix.node-version }}
    - run: npm ci
    - run: npm run build --if-present
    - run: npm test


================================================
FILE: .gitignore
================================================
node_modules
dist


================================================
FILE: .markdownlint.json
================================================
{
  "default": true,
  "no-inline-html": false
}


================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) 2014
  - Kevin Jahns <kevin.jahns@rwth-aachen.de>.
  - Chair of Computer Science 5 (Databases & Information Systems), RWTH Aachen University, Germany

Permission is hereby granted, free of charge, to any person obtaining a copy
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.


================================================
FILE: README.md
================================================
# y-indexeddb

> IndexedDB database provider for Yjs. [Documentation](https://docs.yjs.dev/ecosystem/database-provider/y-indexeddb)

Use the IndexedDB database adapter to store your shared data persistently in
the browser. The next time you join the session, your changes will still be
there.

* Minimizes the amount of data exchanged between server and client
* Makes offline editing possible

## Getting Started

You find the complete documentation published online: [API documentation](https://docs.yjs.dev/ecosystem/database-provider/y-indexeddb).

```sh
npm i --save y-indexeddb
```

```js
const provider = new IndexeddbPersistence(docName, ydoc)

provider.on('synced', () => {
  console.log('content from the database is loaded')
})
```

## API

<dl>
  <b><code>provider = new IndexeddbPersistence(docName: string, ydoc: Y.Doc)</code></b>
  <dd>
Create a y-indexeddb persistence provider. Specify docName as a unique string
that identifies this document. In most cases, you want to use the same identifier
that is used as the room-name in the connection provider.
  </dd>
  <b><code>provider.on('synced', function(idbPersistence: IndexeddbPersistence))</code></b>
  <dd>
The "synced" event is fired when the connection to the database has been established
and all available content has been loaded. The event is also fired if no content
is found for the given doc name.
  </dd>
  <b><code>provider.set(key: any, value: any): Promise&lt;any&gt;</code></b>
  <dd>
Set a custom property on the provider instance. You can use this to store relevant
meta-information for the persisted document. However, the content will not be
synced with other peers.
  </dd>
  <b><code>provider.get(key: any): Promise&gt;any&lt;</code></b>
  <dd>
Retrieve a stored value.
  </dd>
  <b><code>provider.del(key: any): Promise&gt;undefined&lt;</code></b>
  <dd>
Delete a stored value.
  </dd>
  <b><code>provider.destroy(): Promise</code></b>
  <dd>
Close the connection to the database and stop syncing the document. This method is
automatically called when the Yjs document is destroyed (e.g. ydoc.destroy()).
  </dd>
  <b><code>provider.clearData(): Promise</code></b>
  <dd>
Destroy this database and remove the stored document and all related meta-information
from the database.
  </dd>
</dl>

## License

Yjs is licensed under the [MIT License](./LICENSE).

<kevin.jahns@protonmail.com>


================================================
FILE: index.html
================================================
<!DOCTYPE html>
<html>
<head>
  <title>Testing y-indexeddb</title>
</head>
<body>
  <script type="module" src="./dist/test.js"></script>
</body>
</html>


================================================
FILE: package.json
================================================
{
  "name": "y-indexeddb",
  "version": "9.0.12",
  "description": "IndexedDB database adapter for Yjs",
  "type": "module",
  "main": "./dist/y-indexeddb.cjs",
  "module": "./src/y-indexeddb.js",
  "types": "./dist/src/y-indexeddb.d.ts",
  "sideEffects": false,
  "funding": {
    "type": "GitHub Sponsors ❤",
    "url": "https://github.com/sponsors/dmonad"
  },
  "scripts": {
    "clean": "rm -rf dist",
    "test": "npm run lint",
    "dist": "rollup -c",
    "lint": "markdownlint README.md && standard && tsc",
    "preversion": "npm run clean && npm run lint && npm run dist",
    "debug": "concurrently 'rollup -wc' 'http-server -o .'"
  },
  "files": [
    "dist/*",
    "src/*"
  ],
  "exports": {
    ".": {
      "types": "./dist/src/y-indexeddb.d.ts",
      "module": "./src/y-indexeddb.js",
      "import": "./src/y-indexeddb.js",
      "require": "./dist/y-indexeddb.cjs",
      "default": "./src/y-indexeddb.js"
    },
    "./package.json": "./package.json"
  },
  "standard": {
    "ignore": [
      "/dist",
      "/node_modules",
      "/docs"
    ]
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/yjs/y-indexeddb.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-indexeddb/issues"
  },
  "homepage": "https://yjs.dev",
  "dependencies": {
    "lib0": "^0.2.74"
  },
  "devDependencies": {
    "@rollup/plugin-commonjs": "^11.1.0",
    "@rollup/plugin-node-resolve": "^7.1.3",
    "concurrently": "^3.6.1",
    "http-server": "^0.12.3",
    "jsdoc": "^3.6.6",
    "markdownlint-cli": "^0.19.0",
    "rollup": "^1.32.1",
    "standard": "^11.0.1",
    "typescript": "^5.0.4",
    "y-protocols": "^1.0.1",
    "yjs": "^13.4.7"
  },
  "peerDependencies": {
    "yjs": "^13.0.0"
  },
  "engines": {
    "npm": ">=8.0.0",
    "node": ">=16.0.0"
  }
}


================================================
FILE: rollup.config.js
================================================
import resolve from '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs'

export default [{
  input: './tests/index.js',
  output: {
    file: './dist/test.js',
    format: 'iife',
    sourcemap: true
  },
  plugins: [
    resolve({ mainFields: ['module', 'browser', 'main'] }),
    commonjs()
  ]
}, {
  input: './src/y-indexeddb.js',
  output: {
    name: 'Y',
    file: 'dist/y-indexeddb.cjs',
    format: 'cjs',
    sourcemap: true
  },
  external: id => /^(lib0|yjs)\//.test(id)
}]


================================================
FILE: src/y-indexeddb.js
================================================
import * as Y from 'yjs'
import * as idb from 'lib0/indexeddb'
import * as promise from 'lib0/promise'
import { Observable } from 'lib0/observable'

const customStoreName = 'custom'
const updatesStoreName = 'updates'

export const PREFERRED_TRIM_SIZE = 500

/**
 * @param {IndexeddbPersistence} idbPersistence
 * @param {function(IDBObjectStore):void} [beforeApplyUpdatesCallback]
 * @param {function(IDBObjectStore):void} [afterApplyUpdatesCallback]
 */
export const fetchUpdates = (idbPersistence, beforeApplyUpdatesCallback = () => {}, afterApplyUpdatesCallback = () => {}) => {
  const [updatesStore] = idb.transact(/** @type {IDBDatabase} */ (idbPersistence.db), [updatesStoreName]) // , 'readonly')
  return idb.getAll(updatesStore, idb.createIDBKeyRangeLowerBound(idbPersistence._dbref, false)).then(updates => {
    if (!idbPersistence._destroyed) {
      beforeApplyUpdatesCallback(updatesStore)
      Y.transact(idbPersistence.doc, () => {
        updates.forEach(val => Y.applyUpdate(idbPersistence.doc, val))
      }, idbPersistence, false)
      afterApplyUpdatesCallback(updatesStore)
    }
  })
    .then(() => idb.getLastKey(updatesStore).then(lastKey => { idbPersistence._dbref = lastKey + 1 }))
    .then(() => idb.count(updatesStore).then(cnt => { idbPersistence._dbsize = cnt }))
    .then(() => updatesStore)
}

/**
 * @param {IndexeddbPersistence} idbPersistence
 * @param {boolean} forceStore
 */
export const storeState = (idbPersistence, forceStore = true) =>
  fetchUpdates(idbPersistence)
    .then(updatesStore => {
      if (forceStore || idbPersistence._dbsize >= PREFERRED_TRIM_SIZE) {
        idb.addAutoKey(updatesStore, Y.encodeStateAsUpdate(idbPersistence.doc))
          .then(() => idb.del(updatesStore, idb.createIDBKeyRangeUpperBound(idbPersistence._dbref, true)))
          .then(() => idb.count(updatesStore).then(cnt => { idbPersistence._dbsize = cnt }))
      }
    })

/**
 * @param {string} name
 */
export const clearDocument = name => idb.deleteDB(name)

/**
 * @extends Observable<string>
 */
export class IndexeddbPersistence extends Observable {
  /**
   * @param {string} name
   * @param {Y.Doc} doc
   */
  constructor (name, doc) {
    super()
    this.doc = doc
    this.name = name
    this._dbref = 0
    this._dbsize = 0
    this._destroyed = false
    /**
     * @type {IDBDatabase|null}
     */
    this.db = null
    this.synced = false
    this._db = idb.openDB(name, db =>
      idb.createStores(db, [
        ['updates', { autoIncrement: true }],
        ['custom']
      ])
    )
    /**
     * @type {Promise<IndexeddbPersistence>}
     */
    this.whenSynced = promise.create(resolve => this.on('synced', () => resolve(this)))

    this._db.then(db => {
      this.db = db
      /**
       * @param {IDBObjectStore} updatesStore
       */
      const beforeApplyUpdatesCallback = (updatesStore) => idb.addAutoKey(updatesStore, Y.encodeStateAsUpdate(doc))
      const afterApplyUpdatesCallback = () => {
        if (this._destroyed) return this
        this.synced = true
        this.emit('synced', [this])
      }
      fetchUpdates(this, beforeApplyUpdatesCallback, afterApplyUpdatesCallback)
    })
    /**
     * Timeout in ms until data is merged and persisted in idb.
     */
    this._storeTimeout = 1000
    /**
     * @type {any}
     */
    this._storeTimeoutId = null
    /**
     * @param {Uint8Array} update
     * @param {any} origin
     */
    this._storeUpdate = (update, origin) => {
      if (this.db && origin !== this) {
        const [updatesStore] = idb.transact(/** @type {IDBDatabase} */ (this.db), [updatesStoreName])
        idb.addAutoKey(updatesStore, update)
        if (++this._dbsize >= PREFERRED_TRIM_SIZE) {
          // debounce store call
          if (this._storeTimeoutId !== null) {
            clearTimeout(this._storeTimeoutId)
          }
          this._storeTimeoutId = setTimeout(() => {
            storeState(this, false)
            this._storeTimeoutId = null
          }, this._storeTimeout)
        }
      }
    }
    doc.on('update', this._storeUpdate)
    this.destroy = this.destroy.bind(this)
    doc.on('destroy', this.destroy)
  }

  destroy () {
    if (this._storeTimeoutId) {
      clearTimeout(this._storeTimeoutId)
    }
    this.doc.off('update', this._storeUpdate)
    this.doc.off('destroy', this.destroy)
    this._destroyed = true
    return this._db.then(db => {
      db.close()
    })
  }

  /**
   * Destroys this instance and removes all data from indexeddb.
   *
   * @return {Promise<void>}
   */
  clearData () {
    return this.destroy().then(() => {
      idb.deleteDB(this.name)
    })
  }

  /**
   * @param {String | number | ArrayBuffer | Date} key
   * @return {Promise<String | number | ArrayBuffer | Date | any>}
   */
  get (key) {
    return this._db.then(db => {
      const [custom] = idb.transact(db, [customStoreName], 'readonly')
      return idb.get(custom, key)
    })
  }

  /**
   * @param {String | number | ArrayBuffer | Date} key
   * @param {String | number | ArrayBuffer | Date} value
   * @return {Promise<String | number | ArrayBuffer | Date>}
   */
  set (key, value) {
    return this._db.then(db => {
      const [custom] = idb.transact(db, [customStoreName])
      return idb.put(custom, value, key)
    })
  }

  /**
   * @param {String | number | ArrayBuffer | Date} key
   * @return {Promise<undefined>}
   */
  del (key) {
    return this._db.then(db => {
      const [custom] = idb.transact(db, [customStoreName])
      return idb.del(custom, key)
    })
  }
}


================================================
FILE: tests/index.js
================================================

import * as indexeddb from './y-indexeddb.tests.js'

import { runTests } from 'lib0/testing.js'
import { isBrowser, isNode } from 'lib0/environment.js'
import * as log from 'lib0/logging.js'

if (isBrowser) {
  log.createVConsole(document.body)
}
runTests({
  indexeddb
}).then(success => {
  /* istanbul ignore next */
  if (isNode) {
    process.exit(success ? 0 : 1)
  }
})


================================================
FILE: tests/y-indexeddb.tests.js
================================================

import * as Y from 'yjs'
import { IndexeddbPersistence, clearDocument, PREFERRED_TRIM_SIZE, fetchUpdates } from '../src/y-indexeddb.js'
import * as t from 'lib0/testing.js'
import * as promise from 'lib0/promise.js'

/**
 * @param {t.TestCase} tc
 */
export const testPerf = async tc => {
  await t.measureTimeAsync('time to create a y-indexeddb instance', async () => {
    const ydoc = new Y.Doc()
    const provider = new IndexeddbPersistence(tc.testName, ydoc)
    await provider.whenSynced
    provider.destroy()
  })
}

/**
 * @param {t.TestCase} tc
 */
export const testIdbUpdateAndMerge = async tc => {
  await clearDocument(tc.testName)
  const doc1 = new Y.Doc()
  const arr1 = doc1.getArray('t')
  const doc2 = new Y.Doc()
  const arr2 = doc2.getArray('t')
  arr1.insert(0, [0])
  const persistence1 = new IndexeddbPersistence(tc.testName, doc1)
  persistence1._storeTimeout = 0
  await persistence1.whenSynced
  arr1.insert(0, [1])
  const persistence2 = new IndexeddbPersistence(tc.testName, doc2)
  persistence2._storeTimeout = 0
  let calledObserver = false
  // @ts-ignore
  arr2.observe((event, tr) => {
    t.assert(!tr.local)
    t.assert(tr.origin === persistence2)
    calledObserver = true
  })
  await persistence2.whenSynced
  t.assert(calledObserver)
  t.assert(arr2.length === 2)
  for (let i = 2; i < PREFERRED_TRIM_SIZE + 1; i++) {
    arr1.insert(i, [i])
  }
  await promise.wait(100)
  await fetchUpdates(persistence2)
  t.assert(arr2.length === PREFERRED_TRIM_SIZE + 1)
  t.assert(persistence1._dbsize === 1) // wait for dbsize === 0. db should be concatenated
}

/**
 * @param {t.TestCase} tc
 */
export const testIdbConcurrentMerge = async tc => {
  await clearDocument(tc.testName)
  const doc1 = new Y.Doc()
  const arr1 = doc1.getArray('t')
  const doc2 = new Y.Doc()
  const arr2 = doc2.getArray('t')
  arr1.insert(0, [0])
  const persistence1 = new IndexeddbPersistence(tc.testName, doc1)
  persistence1._storeTimeout = 0
  await persistence1.whenSynced
  arr1.insert(0, [1])
  const persistence2 = new IndexeddbPersistence(tc.testName, doc2)
  persistence2._storeTimeout = 0
  await persistence2.whenSynced
  t.assert(arr2.length === 2)
  arr1.insert(0, ['left'])
  for (let i = 0; i < PREFERRED_TRIM_SIZE + 1; i++) {
    arr1.insert(i, [i])
  }
  arr2.insert(0, ['right'])
  for (let i = 0; i < PREFERRED_TRIM_SIZE + 1; i++) {
    arr2.insert(i, [i])
  }
  await promise.wait(100)
  await fetchUpdates(persistence1)
  await fetchUpdates(persistence2)
  t.assert(persistence1._dbsize < 10)
  t.assert(persistence2._dbsize < 10)
  t.compareArrays(arr1.toArray(), arr2.toArray())
}

/**
 * @param {t.TestCase} tc
 */
export const testMetaStorage = async tc => {
  await clearDocument(tc.testName)
  const ydoc = new Y.Doc()
  const persistence = new IndexeddbPersistence(tc.testName, ydoc)
  persistence.set('a', 4)
  persistence.set(4, 'meta!')
  // @ts-ignore
  persistence.set('obj', { a: 4 })
  const resA = await persistence.get('a')
  t.assert(resA === 4)
  const resB = await persistence.get(4)
  t.assert(resB === 'meta!')
  const resC = await persistence.get('obj')
  t.compareObjects(resC, { a: 4 })
}

/**
 * @param {t.TestCase} tc
 */
export const testEarlyDestroy = async tc => {
  let hasbeenSyced = false
  const ydoc = new Y.Doc()
  const indexDBProvider = new IndexeddbPersistence(tc.testName, ydoc)
  indexDBProvider.on('synced', () => {
    hasbeenSyced = true
  })
  indexDBProvider.destroy()
  await new Promise((resolve) => setTimeout(resolve, 500))
  t.assert(!hasbeenSyced)
}


================================================
FILE: tsconfig.json
================================================
{
  "compilerOptions": {
    "target": "es2018",
    "lib": ["es2018", "dom"],
    "allowJs": true,
    "checkJs": true,
    "declaration": true,
    "declarationMap": true,
    "emitDeclarationOnly": true,
    "strict": true,
    "noImplicitAny": true,
    "moduleResolution": "node",
    "outDir": "./dist"
  },
  "include": ["./src/**/*", "./tests/**/*"],
  "exclude": ["../lib0/**/*", "node_modules/**/*", "dist", "dist/**/*.js"]
}
Download .txt
gitextract_7i4vyn17/

├── .github/
│   └── workflows/
│       └── node.js.yml
├── .gitignore
├── .markdownlint.json
├── LICENSE
├── README.md
├── index.html
├── package.json
├── rollup.config.js
├── src/
│   └── y-indexeddb.js
├── tests/
│   ├── index.js
│   └── y-indexeddb.tests.js
└── tsconfig.json
Download .txt
SYMBOL INDEX (8 symbols across 1 files)

FILE: src/y-indexeddb.js
  constant PREFERRED_TRIM_SIZE (line 9) | const PREFERRED_TRIM_SIZE = 500
  class IndexeddbPersistence (line 54) | class IndexeddbPersistence extends Observable {
    method constructor (line 59) | constructor (name, doc) {
    method destroy (line 128) | destroy () {
    method clearData (line 145) | clearData () {
    method get (line 155) | get (key) {
    method set (line 167) | set (key, value) {
    method del (line 178) | del (key) {
Condensed preview — 12 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (18K chars).
[
  {
    "path": ".github/workflows/node.js.yml",
    "chars": 726,
    "preview": "# This workflow will do a clean install of node dependencies, build the source code and run tests across different versi"
  },
  {
    "path": ".gitignore",
    "chars": 18,
    "preview": "node_modules\ndist\n"
  },
  {
    "path": ".markdownlint.json",
    "chars": 49,
    "preview": "{\n  \"default\": true,\n  \"no-inline-html\": false\n}\n"
  },
  {
    "path": "LICENSE",
    "chars": 1211,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2014\n  - Kevin Jahns <kevin.jahns@rwth-aachen.de>.\n  - Chair of Computer Science 5 "
  },
  {
    "path": "README.md",
    "chars": 2376,
    "preview": "# y-indexeddb\n\n> IndexedDB database provider for Yjs. [Documentation](https://docs.yjs.dev/ecosystem/database-provider/y"
  },
  {
    "path": "index.html",
    "chars": 153,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n  <title>Testing y-indexeddb</title>\n</head>\n<body>\n  <script type=\"module\" src=\"./dist/te"
  },
  {
    "path": "package.json",
    "chars": 1999,
    "preview": "{\n  \"name\": \"y-indexeddb\",\n  \"version\": \"9.0.12\",\n  \"description\": \"IndexedDB database adapter for Yjs\",\n  \"type\": \"modu"
  },
  {
    "path": "rollup.config.js",
    "chars": 511,
    "preview": "import resolve from '@rollup/plugin-node-resolve'\nimport commonjs from '@rollup/plugin-commonjs'\n\nexport default [{\n  in"
  },
  {
    "path": "src/y-indexeddb.js",
    "chars": 5540,
    "preview": "import * as Y from 'yjs'\nimport * as idb from 'lib0/indexeddb'\nimport * as promise from 'lib0/promise'\nimport { Observab"
  },
  {
    "path": "tests/index.js",
    "chars": 378,
    "preview": "\nimport * as indexeddb from './y-indexeddb.tests.js'\n\nimport { runTests } from 'lib0/testing.js'\nimport { isBrowser, isN"
  },
  {
    "path": "tests/y-indexeddb.tests.js",
    "chars": 3538,
    "preview": "\nimport * as Y from 'yjs'\nimport { IndexeddbPersistence, clearDocument, PREFERRED_TRIM_SIZE, fetchUpdates } from '../src"
  },
  {
    "path": "tsconfig.json",
    "chars": 436,
    "preview": "{\n  \"compilerOptions\": {\n    \"target\": \"es2018\",\n    \"lib\": [\"es2018\", \"dom\"],\n    \"allowJs\": true,\n    \"checkJs\": true,"
  }
]

About this extraction

This page contains the full source code of the yjs/y-indexeddb GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 12 files (16.5 KB), approximately 5.0k tokens, and a symbol index with 8 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!