@live-change/prosemirror-service 0.2.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/definition.js +11 -0
- package/index.js +120 -0
- package/model.js +121 -0
- package/package.json +32 -0
package/definition.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const app = require("@live-change/framework").app()
|
|
2
|
+
|
|
3
|
+
const relationsPlugin = require('@live-change/relations-plugin')
|
|
4
|
+
const userService = require('@live-change/user-service')
|
|
5
|
+
|
|
6
|
+
const definition = app.createServiceDefinition({
|
|
7
|
+
name: "prosemirror",
|
|
8
|
+
use: [ relationsPlugin, userService ]
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
module.exports = definition
|
package/index.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
const App = require("@live-change/framework")
|
|
2
|
+
const app = App.app()
|
|
3
|
+
|
|
4
|
+
const definition = require('./definition.js')
|
|
5
|
+
|
|
6
|
+
const { Document, StepsBucket, schemas } = require("./model.js")
|
|
7
|
+
|
|
8
|
+
definition.view({
|
|
9
|
+
name: 'document',
|
|
10
|
+
properties: {
|
|
11
|
+
document: {
|
|
12
|
+
type: Document,
|
|
13
|
+
validation: ['nonEmpty']
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
returns: {
|
|
17
|
+
type: Document
|
|
18
|
+
},
|
|
19
|
+
daoPath({ document }, { client, context }) {
|
|
20
|
+
return Document.path( document )
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
definition.view({
|
|
25
|
+
name: 'steps',
|
|
26
|
+
properties: {
|
|
27
|
+
document: {
|
|
28
|
+
type: Document,
|
|
29
|
+
validation: ['nonEmpty']
|
|
30
|
+
},
|
|
31
|
+
...App.rangeProperties
|
|
32
|
+
},
|
|
33
|
+
returns: {
|
|
34
|
+
type: Array,
|
|
35
|
+
of: {
|
|
36
|
+
type: StepsBucket
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
daoPath(props, { client, context }) {
|
|
40
|
+
const path = StepsBucket.rangePath([props.document], App.extractRange(props))
|
|
41
|
+
console.log("PATH", path)
|
|
42
|
+
return path
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
definition.action({
|
|
47
|
+
name: 'createDocument',
|
|
48
|
+
waitForEvents: true,
|
|
49
|
+
properties: {
|
|
50
|
+
document: {
|
|
51
|
+
type: String,
|
|
52
|
+
validation: ['nonEmpty']
|
|
53
|
+
},
|
|
54
|
+
type: {
|
|
55
|
+
type: String,
|
|
56
|
+
validation: ['nonEmpty']
|
|
57
|
+
},
|
|
58
|
+
purpose: {
|
|
59
|
+
type: String,
|
|
60
|
+
validation: ['nonEmpty']
|
|
61
|
+
},
|
|
62
|
+
content: {
|
|
63
|
+
type: Object
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
async execute({ document, type, purpose, content }, { client, service }, emit) {
|
|
67
|
+
if(!schemas[type]) throw new Error(`schema not found for document type ${type}`)
|
|
68
|
+
const documentData = await Document.get(document)
|
|
69
|
+
if(documentData) throw new Error('document already exists')
|
|
70
|
+
emit({
|
|
71
|
+
type: 'documentCreated',
|
|
72
|
+
document, documentType: type, purpose, content, lastModified: new Date(), created: new Date()
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
definition.action({
|
|
78
|
+
name: 'doSteps',
|
|
79
|
+
waitForEvents: true,
|
|
80
|
+
properties: {
|
|
81
|
+
document: {
|
|
82
|
+
type: String,
|
|
83
|
+
validation: ['nonEmpty']
|
|
84
|
+
},
|
|
85
|
+
type: {
|
|
86
|
+
type: String,
|
|
87
|
+
validation: ['nonEmpty']
|
|
88
|
+
},
|
|
89
|
+
version: {
|
|
90
|
+
type: Number
|
|
91
|
+
},
|
|
92
|
+
steps: {
|
|
93
|
+
type: Array,
|
|
94
|
+
validation: ['nonEmpty']
|
|
95
|
+
},
|
|
96
|
+
window: {
|
|
97
|
+
type: String,
|
|
98
|
+
validation: ['nonEmpty']
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
queuedBy: (command) => command.client.document,
|
|
102
|
+
async execute({ document, type, version, steps, window }, { client, service }, emit) {
|
|
103
|
+
if(!schemas[type]) throw new Error(`schema not found for document type ${type}`)
|
|
104
|
+
const documentData = await Document.get(document)
|
|
105
|
+
if(!documentData) throw new Error('document not found')
|
|
106
|
+
if(document.version > version) return 'ignored'
|
|
107
|
+
const [sessionOrUserType, sessionOrUser] =
|
|
108
|
+
client.user ? ['user_User', client.user] : ['session_Session', client.session]
|
|
109
|
+
emit({
|
|
110
|
+
type: 'documentEdited',
|
|
111
|
+
document, documentType: type, version, steps, window,
|
|
112
|
+
sessionOrUserType,
|
|
113
|
+
sessionOrUser,
|
|
114
|
+
timestamp: new Date()
|
|
115
|
+
})
|
|
116
|
+
return 'processed'
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
module.exports = definition
|
package/model.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
const App = require("@live-change/framework")
|
|
2
|
+
const app = App.app()
|
|
3
|
+
const definition = require('./definition.js')
|
|
4
|
+
const config = definition.config
|
|
5
|
+
const LRU = require('lru-cache')
|
|
6
|
+
const { Schema } = require('prosemirror-model')
|
|
7
|
+
const { Step } = require('prosemirror-transform')
|
|
8
|
+
|
|
9
|
+
const Document = definition.model({
|
|
10
|
+
name: 'Document',
|
|
11
|
+
properties: {
|
|
12
|
+
type: {
|
|
13
|
+
type: String
|
|
14
|
+
},
|
|
15
|
+
purpose: {
|
|
16
|
+
type: String
|
|
17
|
+
},
|
|
18
|
+
version: {
|
|
19
|
+
type: Number
|
|
20
|
+
},
|
|
21
|
+
content: {
|
|
22
|
+
type: Object
|
|
23
|
+
},
|
|
24
|
+
created: {
|
|
25
|
+
type: Date
|
|
26
|
+
},
|
|
27
|
+
lastModified: {
|
|
28
|
+
type: Date
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
const StepsBucket = definition.model({
|
|
34
|
+
name: 'StepsBucket',
|
|
35
|
+
properties: {
|
|
36
|
+
steps: {
|
|
37
|
+
type: Object
|
|
38
|
+
},
|
|
39
|
+
sessionOrUserType: {
|
|
40
|
+
type: String
|
|
41
|
+
},
|
|
42
|
+
sessionOrUser: {
|
|
43
|
+
type: String
|
|
44
|
+
},
|
|
45
|
+
window: {
|
|
46
|
+
type: String
|
|
47
|
+
},
|
|
48
|
+
timestamp: {
|
|
49
|
+
type: Date
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
const schemas = {}
|
|
55
|
+
for(const typeName in config.documentTypes) {
|
|
56
|
+
const spec = config.documentTypes[typeName]
|
|
57
|
+
schemas[typeName] = new Schema(spec)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const openDocuments = new LRU({
|
|
61
|
+
max: 500,
|
|
62
|
+
maxSize: 10e6,
|
|
63
|
+
sizeCalculation: (value, key) => value.content.nodeSize,
|
|
64
|
+
ttl: 1000 * 60 * 5,
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
async function getDocument(documentId, documentType) {
|
|
68
|
+
const schema = schemas[documentType]
|
|
69
|
+
if(!schema) throw new Error(`schema not found for document type ${documentType}`)
|
|
70
|
+
let document = openDocuments.get(documentId)
|
|
71
|
+
if(!document) {
|
|
72
|
+
const documentData = await Document.get(documentId)
|
|
73
|
+
if(!documentData) {
|
|
74
|
+
return null
|
|
75
|
+
}
|
|
76
|
+
document = {
|
|
77
|
+
type: documentData.type,
|
|
78
|
+
content: schema.nodeFromJSON(documentData.content),
|
|
79
|
+
version: documentData.version,
|
|
80
|
+
schema
|
|
81
|
+
}
|
|
82
|
+
openDocuments.set(documentId, document)
|
|
83
|
+
}
|
|
84
|
+
if(document.type != documentType) throw new Error("wrong document type!")
|
|
85
|
+
return document
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
definition.event({
|
|
89
|
+
name: "documentCreated",
|
|
90
|
+
async execute({ document, documentType, purpose, content, created, lastModified }) {
|
|
91
|
+
await Document.create({ id: document, type: documentType, purpose, content, created, lastModified, version: 0 })
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
definition.event({
|
|
96
|
+
name: "documentEdited",
|
|
97
|
+
async execute({ document, documentType, version, steps, window, sessionOrUserType, sessionOrUser, timestamp }) {
|
|
98
|
+
const openDocument = await getDocument(document, documentType)
|
|
99
|
+
if(!openDocument) throw new Error('critical error - document not found') /// impossible
|
|
100
|
+
if(openDocument.version != version) return // ignore, client will rebase
|
|
101
|
+
for(const stepJson of steps) {
|
|
102
|
+
const step = Step.fromJSON(openDocument.schema, stepJson)
|
|
103
|
+
openDocument.content = step.apply(openDocument.content).doc
|
|
104
|
+
openDocument.version ++
|
|
105
|
+
}
|
|
106
|
+
await Promise.all([
|
|
107
|
+
Document.update(document, {
|
|
108
|
+
content: openDocument.content.toJSON(),
|
|
109
|
+
version: openDocument.version,
|
|
110
|
+
lastModified: timestamp
|
|
111
|
+
}),
|
|
112
|
+
StepsBucket.create({
|
|
113
|
+
id: App.encodeIdentifier([document, openDocument.version.toFixed().padStart(10, '0')]),
|
|
114
|
+
window, sessionOrUserType, sessionOrUser, timestamp: new Date(),
|
|
115
|
+
steps
|
|
116
|
+
})
|
|
117
|
+
])
|
|
118
|
+
}
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
module.exports = { Document, StepsBucket, schemas }
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@live-change/prosemirror-service",
|
|
3
|
+
"version": "0.2.38",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "NODE_ENV=test tape tests/*"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/live-change/live-change-services.git"
|
|
12
|
+
},
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/live-change/live-change-services/issues"
|
|
16
|
+
},
|
|
17
|
+
"homepage": "https://github.com/live-change/live-change-services",
|
|
18
|
+
"author": {
|
|
19
|
+
"email": "michal@laszczewski.pl",
|
|
20
|
+
"name": "Michał Łaszczewski",
|
|
21
|
+
"url": "https://www.viamage.com/"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@live-change/framework": "0.6.5",
|
|
25
|
+
"@live-change/relations-plugin": "0.6.5",
|
|
26
|
+
"lru-cache": "^7.12.0",
|
|
27
|
+
"pluralize": "8.0.0",
|
|
28
|
+
"progress-stream": "^2.0.0",
|
|
29
|
+
"prosemirror-model": "^1.18.1"
|
|
30
|
+
},
|
|
31
|
+
"gitHead": "d30c93533192fb0d3b620929c61c5d04568beae0"
|
|
32
|
+
}
|