Compare commits
No commits in common. "8b8b7c1bc599f319fe75f223e576984046eb54bf" and "fc6d0374aa71a08c987b6b27f223eb4d4f2cf88b" have entirely different histories.
8b8b7c1bc5
...
fc6d0374aa
@ -1,8 +0,0 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
4
.gitattributes
vendored
4
.gitattributes
vendored
@ -1,4 +0,0 @@
|
||||
/.yarn/** linguist-vendored
|
||||
/.yarn/releases/* binary
|
||||
/.yarn/plugins/**/* binary
|
||||
/.pnp.* binary linguist-generated
|
||||
@ -33,7 +33,8 @@ jobs:
|
||||
--name bpmn-api \
|
||||
--network traefik \
|
||||
--label "traefik.enable=true" \
|
||||
--label "traefik.http.routers.bpmn-api.rule=Host(\`bpmn-api.koptilnya.xyz\`)" \
|
||||
--label "traefik.http.routers.bpmn-api.rule=Host(\`bpmn-api.koptilnya.xyz\`) && PathPrefix(\`/bpmn\`)" \
|
||||
--label "traefik.http.middlewares.bpmn-api.stripprefix.prefixes=/bpmn" \
|
||||
--label "traefik.http.routers.bpmn-api.entrypoints=websecure" \
|
||||
--label "traefik.http.routers.bpmn-api.tls.certresolver=myresolver" \
|
||||
--label "traefik.http.services.bpmn-api.loadbalancer.server.port=80" \
|
||||
|
||||
45
.github/workflows/ci.yml
vendored
Normal file
45
.github/workflows/ci.yml
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
branches:
|
||||
- '**'
|
||||
paths-ignore:
|
||||
- 'dist/**'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [18.x, 16.x, 14.x]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
- name: 📥 Install
|
||||
run: npm install
|
||||
|
||||
- name: Unit tests
|
||||
run: npm test
|
||||
|
||||
- name: ESLint checks
|
||||
run: npm run lint
|
||||
|
||||
- name: Build
|
||||
run: npm run dist
|
||||
|
||||
- name: Preversion
|
||||
run: npm run preversion
|
||||
19
.gitignore
vendored
19
.gitignore
vendored
@ -1,17 +1,4 @@
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
|
||||
# Whether you use PnP or not, the node_modules folder is often used to store
|
||||
# build artifacts that should be gitignored
|
||||
dist
|
||||
node_modules
|
||||
|
||||
# Swap the comments on the following lines if you wish to use zero-installs
|
||||
# In that case, don't forget to run `yarn config set enableGlobalCache false`!
|
||||
# Documentation here: https://yarnpkg.com/features/caching#zero-installs
|
||||
|
||||
#!.yarn/cache
|
||||
.pnp.*
|
||||
tmp
|
||||
dbDir
|
||||
|
||||
8
.idea/.gitignore
generated
vendored
8
.idea/.gitignore
generated
vendored
@ -1,8 +0,0 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
28
.idea/codeStyles/Project.xml
generated
Normal file
28
.idea/codeStyles/Project.xml
generated
Normal file
@ -0,0 +1,28 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<JSCodeStyleSettings version="0">
|
||||
<option name="USE_SEMICOLON_AFTER_STATEMENT" value="false" />
|
||||
<option name="FORCE_SEMICOLON_STYLE" value="true" />
|
||||
<option name="SPACE_BEFORE_GENERATOR_MULT" value="true" />
|
||||
<option name="USE_DOUBLE_QUOTES" value="false" />
|
||||
<option name="FORCE_QUOTE_STYlE" value="true" />
|
||||
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
|
||||
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
|
||||
<option name="SPACES_WITHIN_IMPORTS" value="true" />
|
||||
</JSCodeStyleSettings>
|
||||
<codeStyleSettings language="JavaScript">
|
||||
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
|
||||
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
|
||||
<option name="ALIGN_MULTILINE_FOR" value="false" />
|
||||
<option name="SPACE_BEFORE_METHOD_PARENTHESES" value="true" />
|
||||
<option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
|
||||
<option name="KEEP_SIMPLE_BLOCKS_IN_ONE_LINE" value="true" />
|
||||
<option name="KEEP_SIMPLE_METHODS_IN_ONE_LINE" value="true" />
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="2" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
||||
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
@ -0,0 +1,5 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
</state>
|
||||
</component>
|
||||
6
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
6
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="StandardJS" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
</profile>
|
||||
</component>
|
||||
8
.idea/modules.xml
generated
8
.idea/modules.xml
generated
@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/yjs-backend.iml" filepath="$PROJECT_DIR$/.idea/yjs-backend.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
2
.idea/vcs.xml
generated
2
.idea/vcs.xml
generated
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
162
.idea/workspace.xml
generated
Normal file
162
.idea/workspace.xml
generated
Normal file
@ -0,0 +1,162 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="AutoImportSettings">
|
||||
<option name="autoReloadType" value="SELECTIVE" />
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="52895fea-6930-4acf-96db-7dbc302cd5a2" name="Changes" comment="deploy">
|
||||
<change beforePath="$PROJECT_DIR$/Dockerfile" beforeDir="false" afterPath="$PROJECT_DIR$/Dockerfile" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||
</component>
|
||||
<component name="Git.Settings">
|
||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||
</component>
|
||||
<component name="PackageJsonUpdateNotifier">
|
||||
<dismissed value="$PROJECT_DIR$/package.json" />
|
||||
</component>
|
||||
<component name="ProjectColorInfo">{
|
||||
"associatedIndex": 6
|
||||
}</component>
|
||||
<component name="ProjectId" id="35ee0K0VUzZ5kXiT81QCwaajSuA" />
|
||||
<component name="ProjectViewState">
|
||||
<option name="autoscrollFromSource" value="true" />
|
||||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent"><![CDATA[{
|
||||
"keyToString": {
|
||||
"ModuleVcsDetector.initialDetectionPerformed": "true",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"RunOnceActivity.git.unshallow": "true",
|
||||
"git-widget-placeholder": "main",
|
||||
"last_opened_file_path": "/Users/nvkrug/Work/forms/yjs-backend",
|
||||
"node.js.detected.package.eslint": "true",
|
||||
"node.js.detected.package.tslint": "true",
|
||||
"node.js.selected.package.eslint": "(autodetect)",
|
||||
"node.js.selected.package.tslint": "(autodetect)",
|
||||
"nodejs_package_manager_path": "npm",
|
||||
"vue.rearranger.settings.migration": "true"
|
||||
}
|
||||
}]]></component>
|
||||
<component name="RecentsManager">
|
||||
<key name="CopyFile.RECENT_KEYS">
|
||||
<recent name="$PROJECT_DIR$" />
|
||||
</key>
|
||||
</component>
|
||||
<component name="SharedIndexes">
|
||||
<attachedChunks>
|
||||
<set>
|
||||
<option value="bundled-js-predefined-d6986cc7102b-f27c65a3e318-JavaScript-WS-251.23774.424" />
|
||||
</set>
|
||||
</attachedChunks>
|
||||
</component>
|
||||
<component name="TaskManager">
|
||||
<task active="true" id="Default" summary="Default task">
|
||||
<changelist id="52895fea-6930-4acf-96db-7dbc302cd5a2" name="Changes" comment="" />
|
||||
<created>1763474739965</created>
|
||||
<option name="number" value="Default" />
|
||||
<option name="presentableId" value="Default" />
|
||||
<updated>1763474739965</updated>
|
||||
<workItem from="1763474741250" duration="1322000" />
|
||||
<workItem from="1763559458059" duration="3124000" />
|
||||
<workItem from="1763650898532" duration="4224000" />
|
||||
<workItem from="1763671564081" duration="114000" />
|
||||
</task>
|
||||
<task id="LOCAL-00001" summary="Initial commit">
|
||||
<option name="closed" value="true" />
|
||||
<created>1763650921933</created>
|
||||
<option name="number" value="00001" />
|
||||
<option name="presentableId" value="LOCAL-00001" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1763650921933</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00002" summary="deploy">
|
||||
<option name="closed" value="true" />
|
||||
<created>1763652368961</created>
|
||||
<option name="number" value="00002" />
|
||||
<option name="presentableId" value="LOCAL-00002" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1763652368961</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00003" summary="deploy">
|
||||
<option name="closed" value="true" />
|
||||
<created>1763652697122</created>
|
||||
<option name="number" value="00003" />
|
||||
<option name="presentableId" value="LOCAL-00003" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1763652697122</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00004" summary="deploy">
|
||||
<option name="closed" value="true" />
|
||||
<created>1763652748434</created>
|
||||
<option name="number" value="00004" />
|
||||
<option name="presentableId" value="LOCAL-00004" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1763652748434</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00005" summary="deploy">
|
||||
<option name="closed" value="true" />
|
||||
<created>1763652805038</created>
|
||||
<option name="number" value="00005" />
|
||||
<option name="presentableId" value="LOCAL-00005" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1763652805038</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00006" summary="deploy">
|
||||
<option name="closed" value="true" />
|
||||
<created>1763653055665</created>
|
||||
<option name="number" value="00006" />
|
||||
<option name="presentableId" value="LOCAL-00006" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1763653055665</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00007" summary="deploy">
|
||||
<option name="closed" value="true" />
|
||||
<created>1763653146312</created>
|
||||
<option name="number" value="00007" />
|
||||
<option name="presentableId" value="LOCAL-00007" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1763653146312</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00008" summary="deploy">
|
||||
<option name="closed" value="true" />
|
||||
<created>1763653348112</created>
|
||||
<option name="number" value="00008" />
|
||||
<option name="presentableId" value="LOCAL-00008" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1763653348112</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00009" summary="deploy">
|
||||
<option name="closed" value="true" />
|
||||
<created>1763653685850</created>
|
||||
<option name="number" value="00009" />
|
||||
<option name="presentableId" value="LOCAL-00009" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1763653685850</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00010" summary="deploy">
|
||||
<option name="closed" value="true" />
|
||||
<created>1763654828401</created>
|
||||
<option name="number" value="00010" />
|
||||
<option name="presentableId" value="LOCAL-00010" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1763654828401</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="11" />
|
||||
<servers />
|
||||
</component>
|
||||
<component name="TypeScriptGeneratedFilesManager">
|
||||
<option name="version" value="3" />
|
||||
</component>
|
||||
<component name="VcsManagerConfiguration">
|
||||
<option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="false" />
|
||||
<option name="CHECK_NEW_TODO" value="false" />
|
||||
<MESSAGE value="Initial commit" />
|
||||
<MESSAGE value="deploy" />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="deploy" />
|
||||
</component>
|
||||
</project>
|
||||
12
.idea/yjs-backend.iml
generated
12
.idea/yjs-backend.iml
generated
@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
14
.vscode/launch.json
vendored
Normal file
14
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "websocket server",
|
||||
"program": "${workspaceFolder}/bin/server.js"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
nodeLinker: node-modules
|
||||
20
Dockerfile
20
Dockerfile
@ -1,11 +1,11 @@
|
||||
FROM node:22-alpine AS build
|
||||
WORKDIR /app
|
||||
RUN corepack enable
|
||||
COPY package.json yarn.lock ./
|
||||
RUN yarn config set nodeLinker node-modules
|
||||
RUN yarn install
|
||||
COPY . .
|
||||
EXPOSE 80
|
||||
ENV PORT=80
|
||||
CMD ["npx", "y-websocket"]
|
||||
FROM node:12-alpine
|
||||
|
||||
RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app
|
||||
WORKDIR /home/node/app
|
||||
COPY package*.json ./
|
||||
USER node
|
||||
RUN npm install
|
||||
COPY --chown=node:node . .
|
||||
ENV PORT=80
|
||||
EXPOSE 80
|
||||
CMD [ "npm", "start" ]
|
||||
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2025 Kevin Jahns <kevin.jahns@protonmail.com>.
|
||||
|
||||
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.
|
||||
82
README.md
82
README.md
@ -1 +1,81 @@
|
||||
# yjs-backend
|
||||
|
||||
# y-websocket-server :tophat:
|
||||
> Simple backend for [y-websocket](https://github.com/yjs/y-websocket)
|
||||
|
||||
The Websocket Provider is a solid choice if you want a central source that
|
||||
handles authentication and authorization. Websockets also send header
|
||||
information and cookies, so you can use existing authentication mechanisms with
|
||||
this server.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Install dependencies
|
||||
|
||||
```sh
|
||||
npm i @y/websocket-server
|
||||
```
|
||||
|
||||
### Start a y-websocket server
|
||||
|
||||
This repository implements a basic server that you can adopt to your specific use-case. [(source code)](./src/)
|
||||
|
||||
Start a y-websocket server:
|
||||
|
||||
```sh
|
||||
HOST=localhost PORT=1234 npx y-websocket
|
||||
```
|
||||
|
||||
### Client Code:
|
||||
|
||||
```js
|
||||
import * as Y from 'yjs'
|
||||
import { WebsocketProvider } from 'y-websocket'
|
||||
|
||||
const doc = new Y.Doc()
|
||||
const wsProvider = new WebsocketProvider('ws://localhost:1234', 'my-roomname', doc)
|
||||
|
||||
wsProvider.on('status', event => {
|
||||
console.log(event.status) // logs "connected" or "disconnected"
|
||||
})
|
||||
```
|
||||
|
||||
## Websocket Server
|
||||
|
||||
Start a y-websocket server:
|
||||
|
||||
```sh
|
||||
HOST=localhost PORT=1234 npx y-websocket
|
||||
```
|
||||
|
||||
Since npm symlinks the `y-websocket` executable from your local `./node_modules/.bin` folder, you can simply run npx. The `PORT` environment variable already defaults to 1234, and `HOST` defaults to `localhost`.
|
||||
|
||||
### Websocket Server with Persistence
|
||||
|
||||
Persist document updates in a LevelDB database.
|
||||
|
||||
See [LevelDB Persistence](https://github.com/yjs/y-leveldb) for more info.
|
||||
|
||||
```sh
|
||||
HOST=localhost PORT=1234 YPERSISTENCE=./dbDir npx y-websocket
|
||||
```
|
||||
|
||||
### Websocket Server with HTTP callback
|
||||
|
||||
Send a debounced callback to an HTTP server (`POST`) on document update. Note that this implementation doesn't implement a retry logic in case the `CALLBACK_URL` does not work.
|
||||
|
||||
Can take the following ENV variables:
|
||||
|
||||
* `CALLBACK_URL` : Callback server URL
|
||||
* `CALLBACK_DEBOUNCE_WAIT` : Debounce time between callbacks (in ms). Defaults to 2000 ms
|
||||
* `CALLBACK_DEBOUNCE_MAXWAIT` : Maximum time to wait before callback. Defaults to 10 seconds
|
||||
* `CALLBACK_TIMEOUT` : Timeout for the HTTP call. Defaults to 5 seconds
|
||||
* `CALLBACK_OBJECTS` : JSON of shared objects to get data (`'{"SHARED_OBJECT_NAME":"SHARED_OBJECT_TYPE}'`)
|
||||
|
||||
```sh
|
||||
CALLBACK_URL=http://localhost:3000/ CALLBACK_OBJECTS='{"prosemirror":"XmlFragment"}' npm start
|
||||
```
|
||||
This sends a debounced callback to `localhost:3000` 2 seconds after receiving an update (default `DEBOUNCE_WAIT`) with the data of an XmlFragment named `"prosemirror"` in the body.
|
||||
|
||||
## License
|
||||
|
||||
[The MIT License](./LICENSE) © Kevin Jahns
|
||||
|
||||
4357
package-lock.json
generated
Normal file
4357
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
80
package.json
80
package.json
@ -1,8 +1,80 @@
|
||||
{
|
||||
"name": "yjs-backend",
|
||||
"packageManager": "yarn@4.11.0",
|
||||
"name": "@y/websocket-server",
|
||||
"version": "0.1.1",
|
||||
"description": "Backend for y-websocket",
|
||||
"type": "module",
|
||||
"sideEffects": false,
|
||||
"funding": {
|
||||
"type": "GitHub Sponsors ❤",
|
||||
"url": "https://github.com/sponsors/dmonad"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node ./src/server.js",
|
||||
"dist": "rm -rf dist && rollup -c && tsc",
|
||||
"lint": "standard && tsc",
|
||||
"test": "npm run lint",
|
||||
"preversion": "npm run lint && npm run dist && test -e dist/src/server.d.ts && test -e dist/server.cjs"
|
||||
},
|
||||
"bin": {
|
||||
"y-websocket-server": "src/server.js",
|
||||
"y-websocket": "src/server.js"
|
||||
},
|
||||
"files": [
|
||||
"dist/*",
|
||||
"src/*"
|
||||
],
|
||||
"exports": {
|
||||
"./package.json": "./package.json",
|
||||
"./utils": {
|
||||
"import": "./src/utils.js",
|
||||
"require": "./dist/utils.cjs",
|
||||
"types": "./dist/src/utils.d.ts",
|
||||
"default": "./src/utils.js"
|
||||
},
|
||||
"./callback": {
|
||||
"import": "./src/callback.js",
|
||||
"require": "./dist/callback.cjs",
|
||||
"types": "./dist/src/callback.d.ts",
|
||||
"default": "./src/callback.js"
|
||||
}
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/yjs/y-websocket-server.git"
|
||||
},
|
||||
"keywords": [
|
||||
"Yjs"
|
||||
],
|
||||
"author": "Kevin Jahns <kevin.jahns@protonmail.com>",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/yjs/y-websocket-server/issues"
|
||||
},
|
||||
"homepage": "https://github.com/yjs/y-websocket-server#readme",
|
||||
"standard": {
|
||||
"ignore": [
|
||||
"/dist",
|
||||
"/node_modules"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@y/websocket-server": "^0.1.1",
|
||||
"yjs": "^13.6.27"
|
||||
"@y/protocols": "^1.0.6-1",
|
||||
"lib0": "^0.2.102"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.0.1",
|
||||
"@types/ws": "^8.5.10",
|
||||
"rollup": "^4.39.0",
|
||||
"standard": "^17.1.2",
|
||||
"typescript": "^5.8.3",
|
||||
"ws": "^6.2.1",
|
||||
"yjs": "^14.0.0-7"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"yjs": "^14.0.0-7"
|
||||
},
|
||||
"engines": {
|
||||
"npm": ">=8.0.0",
|
||||
"node": ">=16.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
12
rollup.config.mjs
Normal file
12
rollup.config.mjs
Normal file
@ -0,0 +1,12 @@
|
||||
export default [{
|
||||
input: ['./src/server.js', './src/utils.js', './src/callback.js'],
|
||||
external: id => /^(lib0|yjs|@y|ws|lodash\.debounce|http|y-leveldb)/.test(id),
|
||||
output: [{
|
||||
dir: 'dist',
|
||||
format: 'cjs',
|
||||
sourcemap: true,
|
||||
entryFileNames: '[name].cjs',
|
||||
chunkFileNames: '[name]-[hash].cjs'
|
||||
}]
|
||||
}
|
||||
]
|
||||
75
src/callback.js
Normal file
75
src/callback.js
Normal file
@ -0,0 +1,75 @@
|
||||
import http from 'http'
|
||||
import * as number from 'lib0/number'
|
||||
|
||||
const CALLBACK_URL = process.env.CALLBACK_URL ? new URL(process.env.CALLBACK_URL) : null
|
||||
const CALLBACK_TIMEOUT = number.parseInt(process.env.CALLBACK_TIMEOUT || '5000')
|
||||
const CALLBACK_OBJECTS = process.env.CALLBACK_OBJECTS ? JSON.parse(process.env.CALLBACK_OBJECTS) : {}
|
||||
|
||||
export const isCallbackSet = !!CALLBACK_URL
|
||||
|
||||
/**
|
||||
* @param {import('./utils.js').WSSharedDoc} doc
|
||||
*/
|
||||
export const callbackHandler = (doc) => {
|
||||
const room = doc.name
|
||||
const dataToSend = {
|
||||
room,
|
||||
data: {}
|
||||
}
|
||||
const sharedObjectList = Object.keys(CALLBACK_OBJECTS)
|
||||
sharedObjectList.forEach(sharedObjectName => {
|
||||
const sharedObjectType = CALLBACK_OBJECTS[sharedObjectName]
|
||||
dataToSend.data[sharedObjectName] = {
|
||||
type: sharedObjectType,
|
||||
content: getContent(sharedObjectName, sharedObjectType, doc).toJSON()
|
||||
}
|
||||
})
|
||||
CALLBACK_URL && callbackRequest(CALLBACK_URL, CALLBACK_TIMEOUT, dataToSend)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {URL} url
|
||||
* @param {number} timeout
|
||||
* @param {Object} data
|
||||
*/
|
||||
const callbackRequest = (url, timeout, data) => {
|
||||
data = JSON.stringify(data)
|
||||
const options = {
|
||||
hostname: url.hostname,
|
||||
port: url.port,
|
||||
path: url.pathname,
|
||||
timeout,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Length': Buffer.byteLength(data)
|
||||
}
|
||||
}
|
||||
const req = http.request(options)
|
||||
req.on('timeout', () => {
|
||||
console.warn('Callback request timed out.')
|
||||
req.abort()
|
||||
})
|
||||
req.on('error', (e) => {
|
||||
console.error('Callback request error.', e)
|
||||
req.abort()
|
||||
})
|
||||
req.write(data)
|
||||
req.end()
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} objName
|
||||
* @param {string} objType
|
||||
* @param {import('./utils.js').WSSharedDoc} doc
|
||||
*/
|
||||
const getContent = (objName, objType, doc) => {
|
||||
switch (objType) {
|
||||
case 'Array': return doc.getArray(objName)
|
||||
case 'Map': return doc.getMap(objName)
|
||||
case 'Text': return doc.getText(objName)
|
||||
case 'XmlFragment': return doc.getXmlFragment(objName)
|
||||
case 'XmlElement': return doc.getXmlElement(objName)
|
||||
default : return {}
|
||||
}
|
||||
}
|
||||
31
src/server.js
Executable file
31
src/server.js
Executable file
@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import WebSocket from 'ws'
|
||||
import http from 'http'
|
||||
import * as number from 'lib0/number'
|
||||
import { setupWSConnection } from './utils.js'
|
||||
|
||||
const wss = new WebSocket.Server({ noServer: true })
|
||||
const host = process.env.HOST || 'localhost'
|
||||
const port = number.parseInt(process.env.PORT || '1234')
|
||||
|
||||
const server = http.createServer((_request, response) => {
|
||||
response.writeHead(200, { 'Content-Type': 'text/plain' })
|
||||
response.end('okay')
|
||||
})
|
||||
|
||||
wss.on('connection', setupWSConnection)
|
||||
|
||||
server.on('upgrade', (request, socket, head) => {
|
||||
// You may check auth of request here..
|
||||
// Call `wss.HandleUpgrade` *after* you checked whether the client has access
|
||||
// (e.g. by checking cookies, or url parameters).
|
||||
// See https://github.com/websockets/ws#client-authentication
|
||||
wss.handleUpgrade(request, socket, head, /** @param {any} ws */ ws => {
|
||||
wss.emit('connection', ws, request)
|
||||
})
|
||||
})
|
||||
|
||||
server.listen(port, host, () => {
|
||||
console.log(`running at '${host}' on port ${port}`)
|
||||
})
|
||||
280
src/utils.js
Normal file
280
src/utils.js
Normal file
@ -0,0 +1,280 @@
|
||||
import * as Y from 'yjs'
|
||||
import * as syncProtocol from '@y/protocols/sync'
|
||||
import * as awarenessProtocol from '@y/protocols/awareness'
|
||||
|
||||
import * as encoding from 'lib0/encoding'
|
||||
import * as decoding from 'lib0/decoding'
|
||||
import * as map from 'lib0/map'
|
||||
|
||||
import * as eventloop from 'lib0/eventloop'
|
||||
|
||||
import { callbackHandler, isCallbackSet } from './callback.js'
|
||||
|
||||
const CALLBACK_DEBOUNCE_WAIT = parseInt(process.env.CALLBACK_DEBOUNCE_WAIT || '2000')
|
||||
const CALLBACK_DEBOUNCE_MAXWAIT = parseInt(process.env.CALLBACK_DEBOUNCE_MAXWAIT || '10000')
|
||||
|
||||
const debouncer = eventloop.createDebouncer(CALLBACK_DEBOUNCE_WAIT, CALLBACK_DEBOUNCE_MAXWAIT)
|
||||
|
||||
const wsReadyStateConnecting = 0
|
||||
const wsReadyStateOpen = 1
|
||||
const wsReadyStateClosing = 2 // eslint-disable-line
|
||||
const wsReadyStateClosed = 3 // eslint-disable-line
|
||||
|
||||
// disable gc when using snapshots!
|
||||
const gcEnabled = process.env.GC !== 'false' && process.env.GC !== '0'
|
||||
// const persistenceDir = process.env.YPERSISTENCE
|
||||
/**
|
||||
* @type {{bindState: function(string,WSSharedDoc):void, writeState:function(string,WSSharedDoc):Promise<any>, provider: any}|null}
|
||||
*/
|
||||
let persistence = null
|
||||
|
||||
/**
|
||||
* @param {{bindState: function(string,WSSharedDoc):void,
|
||||
* writeState:function(string,WSSharedDoc):Promise<any>,provider:any}|null} persistence_
|
||||
*/
|
||||
export const setPersistence = persistence_ => {
|
||||
persistence = persistence_
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {null|{bindState: function(string,WSSharedDoc):void,
|
||||
* writeState:function(string,WSSharedDoc):Promise<any>}|null} used persistence layer
|
||||
*/
|
||||
export const getPersistence = () => persistence
|
||||
|
||||
/**
|
||||
* @type {Map<string,WSSharedDoc>}
|
||||
*/
|
||||
export const docs = new Map()
|
||||
|
||||
const messageSync = 0
|
||||
const messageAwareness = 1
|
||||
// const messageAuth = 2
|
||||
|
||||
/**
|
||||
* @param {Uint8Array} update
|
||||
* @param {any} _origin
|
||||
* @param {WSSharedDoc} doc
|
||||
* @param {any} _tr
|
||||
*/
|
||||
const updateHandler = (update, _origin, doc, _tr) => {
|
||||
const encoder = encoding.createEncoder()
|
||||
encoding.writeVarUint(encoder, messageSync)
|
||||
syncProtocol.writeUpdate(encoder, update)
|
||||
const message = encoding.toUint8Array(encoder)
|
||||
doc.conns.forEach((_, conn) => send(doc, conn, message))
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {(ydoc: Y.Doc) => Promise<void>}
|
||||
*/
|
||||
let contentInitializor = _ydoc => Promise.resolve()
|
||||
|
||||
/**
|
||||
* This function is called once every time a Yjs document is created. You can
|
||||
* use it to pull data from an external source or initialize content.
|
||||
*
|
||||
* @param {(ydoc: Y.Doc) => Promise<void>} f
|
||||
*/
|
||||
export const setContentInitializor = (f) => {
|
||||
contentInitializor = f
|
||||
}
|
||||
|
||||
export class WSSharedDoc extends Y.Doc {
|
||||
/**
|
||||
* @param {string} name
|
||||
*/
|
||||
constructor (name) {
|
||||
super({ gc: gcEnabled })
|
||||
this.name = name
|
||||
/**
|
||||
* Maps from conn to set of controlled user ids. Delete all user ids from awareness when this conn is closed
|
||||
* @type {Map<Object, Set<number>>}
|
||||
*/
|
||||
this.conns = new Map()
|
||||
/**
|
||||
* @type {awarenessProtocol.Awareness}
|
||||
*/
|
||||
this.awareness = new awarenessProtocol.Awareness(this)
|
||||
this.awareness.setLocalState(null)
|
||||
/**
|
||||
* @param {{ added: Array<number>, updated: Array<number>, removed: Array<number> }} changes
|
||||
* @param {Object | null} conn Origin is the connection that made the change
|
||||
*/
|
||||
const awarenessChangeHandler = ({ added, updated, removed }, conn) => {
|
||||
const changedClients = added.concat(updated, removed)
|
||||
if (conn !== null) {
|
||||
const connControlledIDs = /** @type {Set<number>} */ (this.conns.get(conn))
|
||||
if (connControlledIDs !== undefined) {
|
||||
added.forEach(clientID => { connControlledIDs.add(clientID) })
|
||||
removed.forEach(clientID => { connControlledIDs.delete(clientID) })
|
||||
}
|
||||
}
|
||||
// broadcast awareness update
|
||||
const encoder = encoding.createEncoder()
|
||||
encoding.writeVarUint(encoder, messageAwareness)
|
||||
encoding.writeVarUint8Array(encoder, awarenessProtocol.encodeAwarenessUpdate(this.awareness, changedClients))
|
||||
const buff = encoding.toUint8Array(encoder)
|
||||
this.conns.forEach((_, c) => {
|
||||
send(this, c, buff)
|
||||
})
|
||||
}
|
||||
this.awareness.on('update', awarenessChangeHandler)
|
||||
this.on('update', /** @type {any} */ (updateHandler))
|
||||
if (isCallbackSet) {
|
||||
this.on('update', (_update, _origin, doc) => {
|
||||
debouncer(() => callbackHandler(/** @type {WSSharedDoc} */ (doc)))
|
||||
})
|
||||
}
|
||||
this.whenInitialized = contentInitializor(this)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a Y.Doc by name, whether in memory or on disk
|
||||
*
|
||||
* @param {string} docname - the name of the Y.Doc to find or create
|
||||
* @param {boolean} gc - whether to allow gc on the doc (applies only when created)
|
||||
* @return {WSSharedDoc}
|
||||
*/
|
||||
export const getYDoc = (docname, gc = true) => map.setIfUndefined(docs, docname, () => {
|
||||
const doc = new WSSharedDoc(docname)
|
||||
doc.gc = gc
|
||||
if (persistence !== null) {
|
||||
persistence.bindState(docname, doc)
|
||||
}
|
||||
docs.set(docname, doc)
|
||||
return doc
|
||||
})
|
||||
|
||||
/**
|
||||
* @param {any} conn
|
||||
* @param {WSSharedDoc} doc
|
||||
* @param {Uint8Array} message
|
||||
*/
|
||||
const messageListener = (conn, doc, message) => {
|
||||
try {
|
||||
const encoder = encoding.createEncoder()
|
||||
const decoder = decoding.createDecoder(message)
|
||||
const messageType = decoding.readVarUint(decoder)
|
||||
switch (messageType) {
|
||||
case messageSync:
|
||||
encoding.writeVarUint(encoder, messageSync)
|
||||
syncProtocol.readSyncMessage(decoder, encoder, doc, conn)
|
||||
|
||||
// If the `encoder` only contains the type of reply message and no
|
||||
// message, there is no need to send the message. When `encoder` only
|
||||
// contains the type of reply, its length is 1.
|
||||
if (encoding.length(encoder) > 1) {
|
||||
send(doc, conn, encoding.toUint8Array(encoder))
|
||||
}
|
||||
break
|
||||
case messageAwareness: {
|
||||
awarenessProtocol.applyAwarenessUpdate(doc.awareness, decoding.readVarUint8Array(decoder), conn)
|
||||
break
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
// @ts-ignore
|
||||
doc.emit('error', [err])
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {WSSharedDoc} doc
|
||||
* @param {any} conn
|
||||
*/
|
||||
const closeConn = (doc, conn) => {
|
||||
if (doc.conns.has(conn)) {
|
||||
/**
|
||||
* @type {Set<number>}
|
||||
*/
|
||||
// @ts-ignore
|
||||
const controlledIds = doc.conns.get(conn)
|
||||
doc.conns.delete(conn)
|
||||
awarenessProtocol.removeAwarenessStates(doc.awareness, Array.from(controlledIds), null)
|
||||
if (doc.conns.size === 0 && persistence !== null) {
|
||||
// if persisted, we store state and destroy ydocument
|
||||
persistence.writeState(doc.name, doc).then(() => {
|
||||
doc.destroy()
|
||||
})
|
||||
docs.delete(doc.name)
|
||||
}
|
||||
}
|
||||
conn.close()
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {WSSharedDoc} doc
|
||||
* @param {import('ws').WebSocket} conn
|
||||
* @param {Uint8Array} m
|
||||
*/
|
||||
const send = (doc, conn, m) => {
|
||||
if (conn.readyState !== wsReadyStateConnecting && conn.readyState !== wsReadyStateOpen) {
|
||||
closeConn(doc, conn)
|
||||
}
|
||||
try {
|
||||
conn.send(m, {}, err => { err != null && closeConn(doc, conn) })
|
||||
} catch (e) {
|
||||
closeConn(doc, conn)
|
||||
}
|
||||
}
|
||||
|
||||
const pingTimeout = 30000
|
||||
|
||||
/**
|
||||
* @param {import('ws').WebSocket} conn
|
||||
* @param {import('http').IncomingMessage} req
|
||||
* @param {any} opts
|
||||
*/
|
||||
export const setupWSConnection = (conn, req, { docName = (req.url || '').slice(1).split('?')[0], gc = true } = {}) => {
|
||||
conn.binaryType = 'arraybuffer'
|
||||
// get doc, initialize if it does not exist yet
|
||||
const doc = getYDoc(docName, gc)
|
||||
doc.conns.set(conn, new Set())
|
||||
// listen and reply to events
|
||||
conn.on('message', /** @param {ArrayBuffer} message */ message => messageListener(conn, doc, new Uint8Array(message)))
|
||||
|
||||
// Check if connection is still alive
|
||||
let pongReceived = true
|
||||
const pingInterval = setInterval(() => {
|
||||
if (!pongReceived) {
|
||||
if (doc.conns.has(conn)) {
|
||||
closeConn(doc, conn)
|
||||
}
|
||||
clearInterval(pingInterval)
|
||||
} else if (doc.conns.has(conn)) {
|
||||
pongReceived = false
|
||||
try {
|
||||
conn.ping()
|
||||
} catch (e) {
|
||||
closeConn(doc, conn)
|
||||
clearInterval(pingInterval)
|
||||
}
|
||||
}
|
||||
}, pingTimeout)
|
||||
conn.on('close', () => {
|
||||
closeConn(doc, conn)
|
||||
clearInterval(pingInterval)
|
||||
})
|
||||
conn.on('pong', () => {
|
||||
pongReceived = true
|
||||
})
|
||||
// put the following in a variables in a block so the interval handlers don't keep in in
|
||||
// scope
|
||||
{
|
||||
// send sync step 1
|
||||
const encoder = encoding.createEncoder()
|
||||
encoding.writeVarUint(encoder, messageSync)
|
||||
syncProtocol.writeSyncStep1(encoder, doc)
|
||||
send(doc, conn, encoding.toUint8Array(encoder))
|
||||
const awarenessStates = doc.awareness.getStates()
|
||||
if (awarenessStates.size > 0) {
|
||||
const encoder = encoding.createEncoder()
|
||||
encoding.writeVarUint(encoder, messageAwareness)
|
||||
encoding.writeVarUint8Array(encoder, awarenessProtocol.encodeAwarenessUpdate(doc.awareness, Array.from(awarenessStates.keys())))
|
||||
send(doc, conn, encoding.toUint8Array(encoder))
|
||||
}
|
||||
}
|
||||
}
|
||||
62
tsconfig.json
Normal file
62
tsconfig.json
Normal file
@ -0,0 +1,62 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
/* Basic Options */
|
||||
"target": "es2018",
|
||||
"lib": ["es2018", "dom"], /* Specify library files to be included in the compilation. */
|
||||
"allowJs": true, /* Allow javascript files to be compiled. */
|
||||
"checkJs": true, /* Report errors in .js files. */
|
||||
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
|
||||
"declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
||||
// "sourceMap": true, /* Generates corresponding '.map' file. */
|
||||
// "outFile": "./", /* Concatenate and emit output to single file. */
|
||||
"outDir": "./dist", /* Redirect output structure to the directory. */
|
||||
"rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||
// "composite": true, /* Enable project compilation */
|
||||
// "removeComments": true, /* Do not emit comments to output. */
|
||||
// "noEmit": true, /* Do not emit outputs. */
|
||||
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||
|
||||
/* Strict Type-Checking Options */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
"noImplicitAny": false, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||
"emitDeclarationOnly": true,
|
||||
// "strictNullChecks": true, /* Enable strict null checks. */
|
||||
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
|
||||
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||
|
||||
/* Additional Checks */
|
||||
// "noUnusedLocals": true, /* Report errors on unused locals. */
|
||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||
|
||||
/* Module Resolution Options */
|
||||
"moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||
"baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||
"paths": {
|
||||
},
|
||||
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||
// "types": [], /* Type declaration files to be included in compilation. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||
|
||||
/* Source Map Options */
|
||||
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||
|
||||
/* Experimental Options */
|
||||
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||
// "maxNodeModuleJsDepth": 5
|
||||
},
|
||||
"include": ["./src/**/*"]
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user