@live-change/prosemirror-service 0.2.48 → 0.2.50
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/index.js +117 -4
- package/model.js +97 -7
- package/package.json +4 -4
package/index.js
CHANGED
|
@@ -49,6 +49,53 @@ definition.view({
|
|
|
49
49
|
}
|
|
50
50
|
})
|
|
51
51
|
|
|
52
|
+
definition.view({
|
|
53
|
+
name: 'snapshot',
|
|
54
|
+
properties: {
|
|
55
|
+
document: {
|
|
56
|
+
type: Document,
|
|
57
|
+
validation: ['nonEmpty']
|
|
58
|
+
},
|
|
59
|
+
version: {
|
|
60
|
+
type: Number
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
async daoPath({ document, version }, { client, context }) {
|
|
64
|
+
return Snapshot.path( App.encodeIdentifier([props.document,version.toFixed().padStart(10, '0')]) )
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
definition.view({
|
|
69
|
+
name: 'snapshots',
|
|
70
|
+
properties: {
|
|
71
|
+
document: {
|
|
72
|
+
type: Document,
|
|
73
|
+
validation: ['nonEmpty']
|
|
74
|
+
},
|
|
75
|
+
...App.rangeProperties
|
|
76
|
+
},
|
|
77
|
+
async daoPath({ document, version }, { client, context }) {
|
|
78
|
+
return Snapshot.indexRangePath( [document], App.extractRange(props) )
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
definition.view({
|
|
83
|
+
name: 'snapshots',
|
|
84
|
+
properties: {
|
|
85
|
+
document: {
|
|
86
|
+
type: Document,
|
|
87
|
+
validation: ['nonEmpty']
|
|
88
|
+
},
|
|
89
|
+
version: {
|
|
90
|
+
type: Number
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
async daoPath({ document, version }, { client, context }) {
|
|
94
|
+
Snapshot.limitedRangePath([props.document], { limit: 10 })
|
|
95
|
+
return Snapshot.path( App.encodeIdentifier([props.document,version.toFixed().padStart(10, '0')]) )
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
|
|
52
99
|
definition.action({
|
|
53
100
|
name: 'createDocument',
|
|
54
101
|
waitForEvents: true,
|
|
@@ -160,11 +207,12 @@ definition.action({
|
|
|
160
207
|
const [sessionOrUserType, sessionOrUser] =
|
|
161
208
|
client.user ? ['user_User', client.user] : ['session_Session', client.session]
|
|
162
209
|
if(continuation) {
|
|
163
|
-
console.log("DOC DATA", documentData)
|
|
164
|
-
console.log("CONTINUATION", documentData.lastStepsBucket, sessionOrUserType, sessionOrUser)
|
|
210
|
+
//console.log("DOC DATA", documentData)
|
|
211
|
+
//console.log("CONTINUATION", documentData.lastStepsBucket, sessionOrUserType, sessionOrUser)
|
|
165
212
|
if(!documentData.lastStepsBucket
|
|
166
|
-
|
|
167
|
-
|
|
213
|
+
|| documentData.lastStepsBucket.sessionOrUserType != sessionOrUserType
|
|
214
|
+
|| documentData.lastStepsBucket.sessionOrUser != sessionOrUser
|
|
215
|
+
|| documentData.lastStepsBucket.window != window) {
|
|
168
216
|
console.log("CONTINUATION IGNORED!!")
|
|
169
217
|
return [] // ignore, client will rebase
|
|
170
218
|
}
|
|
@@ -181,4 +229,69 @@ definition.action({
|
|
|
181
229
|
}
|
|
182
230
|
})
|
|
183
231
|
|
|
232
|
+
definition.action({
|
|
233
|
+
name: 'takeSnapshot',
|
|
234
|
+
waitForEvents: true,
|
|
235
|
+
properties: {
|
|
236
|
+
document: {
|
|
237
|
+
type: String,
|
|
238
|
+
validation: ['nonEmpty']
|
|
239
|
+
},
|
|
240
|
+
type: {
|
|
241
|
+
type: String,
|
|
242
|
+
validation: ['nonEmpty']
|
|
243
|
+
},
|
|
244
|
+
version: {
|
|
245
|
+
type: Number
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
queuedBy: (command) => command.client.document,
|
|
249
|
+
async execute({ document, type, version }, { client, service }, emit) {
|
|
250
|
+
if(!schemas[type]) throw new Error(`schema not found for document type ${type}`)
|
|
251
|
+
const documentData = await getDocument(document, type)
|
|
252
|
+
if(!documentData) throw new Error('document not found')
|
|
253
|
+
if(typeof version != 'number') version = documentData.version
|
|
254
|
+
const snapshot = App.encodeIdentifier([document, version.toFixed().padStart(10, '0')])
|
|
255
|
+
emit({
|
|
256
|
+
type: 'snapshotTaken',
|
|
257
|
+
snapshot,
|
|
258
|
+
document, documentType: type, version
|
|
259
|
+
})
|
|
260
|
+
return snapshot
|
|
261
|
+
}
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
definition.trigger({
|
|
265
|
+
name: 'takeSnapshot',
|
|
266
|
+
waitForEvents: true,
|
|
267
|
+
properties: {
|
|
268
|
+
document: {
|
|
269
|
+
type: String,
|
|
270
|
+
validation: ['nonEmpty']
|
|
271
|
+
},
|
|
272
|
+
type: {
|
|
273
|
+
type: String,
|
|
274
|
+
validation: ['nonEmpty']
|
|
275
|
+
},
|
|
276
|
+
version: {
|
|
277
|
+
type: Number
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
queuedBy: (command) => command.client.document,
|
|
281
|
+
async execute({ document, type, version }, { client, service }, emit) {
|
|
282
|
+
if(!schemas[type]) throw new Error(`schema not found for document type ${type}`)
|
|
283
|
+
const documentData = await getDocument(document, type)
|
|
284
|
+
if(!documentData) throw new Error('document not found')
|
|
285
|
+
if(typeof version != 'number') version = documentData.version
|
|
286
|
+
const snapshot = App.encodeIdentifier([document, version.toFixed().padStart(10, '0')])
|
|
287
|
+
emit({
|
|
288
|
+
type: 'snapshotTaken',
|
|
289
|
+
snapshot,
|
|
290
|
+
document, documentType: type, version
|
|
291
|
+
})
|
|
292
|
+
return snapshot
|
|
293
|
+
}
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
|
|
184
297
|
module.exports = definition
|
package/model.js
CHANGED
|
@@ -6,6 +6,8 @@ const LRU = require('lru-cache')
|
|
|
6
6
|
const { Schema } = require('prosemirror-model')
|
|
7
7
|
const { Step } = require('prosemirror-transform')
|
|
8
8
|
|
|
9
|
+
const { snapshotAfterSteps = 230 } = config
|
|
10
|
+
|
|
9
11
|
const Document = definition.model({
|
|
10
12
|
name: 'Document',
|
|
11
13
|
propertyOfAny: {
|
|
@@ -53,6 +55,29 @@ const StepsBucket = definition.model({
|
|
|
53
55
|
}
|
|
54
56
|
})
|
|
55
57
|
|
|
58
|
+
const Snapshot = definition.model({
|
|
59
|
+
name: 'Snapshot',
|
|
60
|
+
properties: {
|
|
61
|
+
document: {
|
|
62
|
+
type: String
|
|
63
|
+
},
|
|
64
|
+
version: {
|
|
65
|
+
type: Number
|
|
66
|
+
},
|
|
67
|
+
content: {
|
|
68
|
+
type: Object
|
|
69
|
+
},
|
|
70
|
+
timestamp: {
|
|
71
|
+
type: Date
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
indexes: {
|
|
75
|
+
list: {
|
|
76
|
+
property: ['document', 'version']
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
|
|
56
81
|
const schemas = {}
|
|
57
82
|
for(const typeName in config.documentTypes) {
|
|
58
83
|
const spec = config.documentTypes[typeName]
|
|
@@ -75,12 +100,16 @@ async function getDocument(documentId, documentType) {
|
|
|
75
100
|
if(!documentData) {
|
|
76
101
|
return null
|
|
77
102
|
}
|
|
78
|
-
const lastStepsBucket = await
|
|
103
|
+
const [lastStepsBucket, lastSnapshot] = await Promise.all([
|
|
104
|
+
StepsBucket.rangeGet([documentId], { reverse: true, limit: 1 }).then(x => x?.[0] ?? null),
|
|
105
|
+
Snapshot.rangeGet([documentId], { reverse: true, limit: 1 }).then(x => x?.[0] ?? null)
|
|
106
|
+
])
|
|
79
107
|
document = {
|
|
80
108
|
type: documentData.type,
|
|
81
109
|
content: schema.nodeFromJSON(documentData.content),
|
|
82
110
|
version: documentData.version,
|
|
83
111
|
lastStepsBucket,
|
|
112
|
+
lastSnapshot,
|
|
84
113
|
schema
|
|
85
114
|
}
|
|
86
115
|
openDocuments.set(documentId, document)
|
|
@@ -92,7 +121,10 @@ async function getDocument(documentId, documentType) {
|
|
|
92
121
|
definition.event({
|
|
93
122
|
name: "documentCreated",
|
|
94
123
|
async execute({ document, documentType, purpose, content, created, lastModified }) {
|
|
95
|
-
|
|
124
|
+
const version = 0
|
|
125
|
+
await Document.create({ id: document, type: documentType, purpose, content, created, lastModified, version })
|
|
126
|
+
await Snapshot.create({ id: App.encodeIdentifier([document, version.toFixed().padStart(10, '0')]),
|
|
127
|
+
document, version, content, timestamp: lastModified })
|
|
96
128
|
}
|
|
97
129
|
})
|
|
98
130
|
|
|
@@ -112,17 +144,75 @@ definition.event({
|
|
|
112
144
|
window, sessionOrUserType, sessionOrUser, timestamp: new Date(),
|
|
113
145
|
steps
|
|
114
146
|
}
|
|
115
|
-
console.log("DOC EDITED", bucket)
|
|
147
|
+
//console.log("DOC EDITED", bucket)
|
|
148
|
+
const content = openDocument.content.toJSON()
|
|
116
149
|
openDocument.lastStepsBucket = bucket
|
|
117
|
-
|
|
150
|
+
const promises = [
|
|
118
151
|
Document.update(document, {
|
|
119
|
-
content
|
|
152
|
+
content,
|
|
120
153
|
version: openDocument.version,
|
|
121
154
|
lastModified: timestamp
|
|
122
155
|
}),
|
|
123
156
|
StepsBucket.create(bucket)
|
|
124
|
-
]
|
|
157
|
+
]
|
|
158
|
+
if(openDocument.lastSnapshot.version < openDocument.version - snapshotAfterSteps) {
|
|
159
|
+
openDocument.lastSnapshot = {
|
|
160
|
+
id: App.encodeIdentifier([document, openDocument.version.toFixed().padStart(10, '0')]),
|
|
161
|
+
document, version: openDocument.version, content, timestamp
|
|
162
|
+
}
|
|
163
|
+
promises.push(Snapshot.create(openDocument.lastSnapshot))
|
|
164
|
+
}
|
|
165
|
+
await Promise.all(promises)
|
|
166
|
+
}
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
async function readVersion(document, documentType, version) {
|
|
170
|
+
const schema = schemas[documentType]
|
|
171
|
+
const snapshot = await Snapshot.rangePath([document], {
|
|
172
|
+
reverse: true, limit: 1, lte: version.toFixed().padStart(10, '0')
|
|
173
|
+
})?.[0]
|
|
174
|
+
if(!snapshot) throw 'not_found'
|
|
175
|
+
let content = schema.nodeFromJSON(snapshot.content)
|
|
176
|
+
let current = snapshot.version
|
|
177
|
+
while(current < version) {
|
|
178
|
+
const stepsBuckets = await StepsBucket.rangePath([document], {
|
|
179
|
+
gt: current.toFixed().padStart(10, '0')
|
|
180
|
+
})
|
|
181
|
+
for(const stepsBucket of stepsBuckets) {
|
|
182
|
+
for(const stepJson of stepsBucket.steps) {
|
|
183
|
+
const step = Step.fromJSON(schemas[snapshot.type], stepJson)
|
|
184
|
+
content = step.apply(content).doc
|
|
185
|
+
current ++
|
|
186
|
+
if(current == version) return {
|
|
187
|
+
content,
|
|
188
|
+
timestamp: stepsBuckets.timestamp
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
definition.event({
|
|
196
|
+
name: "snapshotTaken",
|
|
197
|
+
async execute({ snapshot: id, document, documentType, version }) {
|
|
198
|
+
const openDocument = await getDocument(document, documentType)
|
|
199
|
+
if(!openDocument) throw new Error('critical error - document not found') /// impossible
|
|
200
|
+
if(openDocument.version < version)
|
|
201
|
+
throw new Error('critical error - document version is lower than snapshot version') /// impossible
|
|
202
|
+
const existing = Snapshot.get(id)
|
|
203
|
+
if(existing) return id
|
|
204
|
+
if(openDocument.version == version) {
|
|
205
|
+
const content = openDocument.content
|
|
206
|
+
const snapshot = { id, document, version, content, timestamp: openDocument.lastModified }
|
|
207
|
+
await Snapshot.create(snapshot)
|
|
208
|
+
if(version > openDocument.lastSnapshot.version) openDocument.lastSnapshot = snapshot
|
|
209
|
+
} else {
|
|
210
|
+
const { content, timestamp } = readVersion(document, documentType, version)
|
|
211
|
+
const snapshot = { id, document, version, content, timestamp }
|
|
212
|
+
await Snapshot.create(snapshot)
|
|
213
|
+
if(version > openDocument.lastSnapshot.version) openDocument.lastSnapshot = snapshot
|
|
214
|
+
}
|
|
125
215
|
}
|
|
126
216
|
})
|
|
127
217
|
|
|
128
|
-
module.exports = { Document, StepsBucket, schemas, getDocument }
|
|
218
|
+
module.exports = { Document, StepsBucket, schemas, getDocument, readVersion }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@live-change/prosemirror-service",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.50",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -21,12 +21,12 @@
|
|
|
21
21
|
"url": "https://www.viamage.com/"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@live-change/framework": "0.6.
|
|
25
|
-
"@live-change/relations-plugin": "0.6.
|
|
24
|
+
"@live-change/framework": "0.6.14",
|
|
25
|
+
"@live-change/relations-plugin": "0.6.14",
|
|
26
26
|
"lru-cache": "^7.12.0",
|
|
27
27
|
"pluralize": "8.0.0",
|
|
28
28
|
"progress-stream": "^2.0.0",
|
|
29
29
|
"prosemirror-model": "^1.18.1"
|
|
30
30
|
},
|
|
31
|
-
"gitHead": "
|
|
31
|
+
"gitHead": "8d09f0f01547edd2c1321a8d7341e33aff4c562d"
|
|
32
32
|
}
|