Skip to content

Commit efc5e9f

Browse files
committed
feat(model-client): add all history queries to ClientJS
1 parent 9877d2f commit efc5e9f

File tree

8 files changed

+232
-69
lines changed

8 files changed

+232
-69
lines changed

model-client/src/commonMain/kotlin/org/modelix/model/client2/ModelClientV2.kt

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import org.modelix.datastructures.history.HistoryEntry
4242
import org.modelix.datastructures.history.HistoryIndexNode
4343
import org.modelix.datastructures.history.HistoryInterval
4444
import org.modelix.datastructures.history.IHistoryQueries
45+
import org.modelix.datastructures.history.PaginationParameters
4546
import org.modelix.datastructures.objects.IObjectGraph
4647
import org.modelix.datastructures.objects.Object
4748
import org.modelix.datastructures.objects.ObjectHash
@@ -342,6 +343,7 @@ class ModelClientV2(
342343
override suspend fun sessions(
343344
timeRange: ClosedRange<Instant>?,
344345
delay: Duration,
346+
pagination: PaginationParameters,
345347
): List<HistoryInterval> {
346348
return httpClient.prepareGet {
347349
url {
@@ -352,6 +354,8 @@ class ModelClientV2(
352354
parameters["maxTime"] = timeRange.endInclusive.epochSeconds.toString()
353355
}
354356
parameters["delay"] = delay.inWholeSeconds.toString()
357+
parameters["skip"] = pagination.skip.toString()
358+
parameters["limit"] = pagination.limit.toString()
355359
}
356360
}.execute { response ->
357361
response.body<List<HistoryInterval>>()
@@ -361,6 +365,7 @@ class ModelClientV2(
361365
override suspend fun intervals(
362366
timeRange: ClosedRange<Instant>?,
363367
interval: Duration,
368+
pagination: PaginationParameters,
364369
): List<HistoryInterval> {
365370
return httpClient.prepareGet {
366371
url {
@@ -371,6 +376,8 @@ class ModelClientV2(
371376
parameters["maxTime"] = timeRange.endInclusive.epochSeconds.toString()
372377
}
373378
parameters["duration"] = interval.inWholeSeconds.toString()
379+
parameters["skip"] = pagination.skip.toString()
380+
parameters["limit"] = pagination.limit.toString()
374381
}
375382
}.execute { response ->
376383
response.body<List<HistoryInterval>>()
@@ -379,8 +386,7 @@ class ModelClientV2(
379386

380387
override suspend fun range(
381388
timeRange: ClosedRange<Instant>?,
382-
skip: Long,
383-
limit: Long,
389+
pagination: PaginationParameters,
384390
): List<HistoryEntry> {
385391
return httpClient.prepareGet {
386392
url {
@@ -390,8 +396,8 @@ class ModelClientV2(
390396
parameters["minTime"] = timeRange.start.epochSeconds.toString()
391397
parameters["maxTime"] = timeRange.endInclusive.epochSeconds.toString()
392398
}
393-
parameters["skip"] = skip.toString()
394-
parameters["limit"] = limit.toString()
399+
parameters["skip"] = pagination.skip.toString()
400+
parameters["limit"] = pagination.limit.toString()
395401
}
396402
}.execute { response ->
397403
response.body<List<HistoryEntry>>()
@@ -1077,5 +1083,3 @@ fun IVersion.runWrite(idGenerator: IIdGenerator, author: String?, body: (IBranch
10771083
}
10781084

10791085
private fun String.ensureSuffix(suffix: String) = if (endsWith(suffix)) this else this + suffix
1080-
1081-
private class LimitReached : RuntimeException("limit reached")

model-client/src/jsMain/kotlin/org/modelix/model/client2/ClientJS.kt

Lines changed: 89 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import kotlinx.coroutines.GlobalScope
99
import kotlinx.coroutines.await
1010
import kotlinx.coroutines.promise
1111
import kotlinx.datetime.toJSDate
12+
import kotlinx.datetime.toKotlinInstant
13+
import org.modelix.datastructures.history.PaginationParameters
1214
import org.modelix.datastructures.model.IGenericModelTree
1315
import org.modelix.datastructures.objects.ObjectHash
1416
import org.modelix.model.TreeId
@@ -27,7 +29,9 @@ import org.modelix.model.mutable.load
2729
import org.modelix.model.mutable.withAutoTransactions
2830
import org.modelix.model.persistent.MapBasedStore
2931
import org.modelix.mps.multiplatform.model.MPSIdGenerator
32+
import kotlin.js.Date
3033
import kotlin.js.Promise
34+
import kotlin.time.Duration.Companion.seconds
3135

3236
/**
3337
* Same as [loadModelsFromJsonAsBranch] but directly returns the [MutableModelTreeJs.rootNode] of the created branch.
@@ -113,8 +117,15 @@ interface ClientJS {
113117
*/
114118
fun initRepository(repositoryId: String, useRoleIds: Boolean = true): Promise<Unit>
115119

116-
fun getHistoryRangeForBranch(repositoryId: String, branchId: String, skip: Int, limit: Int): Promise<Array<VersionInformationJS>>
117120
fun getHistoryRange(repositoryId: String, headVersion: String, skip: Int, limit: Int): Promise<Array<VersionInformationJS>>
121+
fun getHistorySessions(repositoryId: String, headVersion: String, delaySeconds: Int, skip: Int, limit: Int): Promise<Array<HistoryIntervalJS>>
122+
fun getHistoryForFixedIntervals(repositoryId: String, headVersion: String, intervalDurationSeconds: Int, skip: Int, limit: Int): Promise<Array<HistoryIntervalJS>>
123+
fun getHistoryForProvidedIntervals(repositoryId: String, headVersion: String, splitAt: Array<Date>): Promise<Array<HistoryIntervalJS>>
124+
125+
fun getHistoryRangeForBranch(repositoryId: String, branchId: String, skip: Int, limit: Int): Promise<Array<VersionInformationJS>>
126+
fun getHistorySessionsForBranch(repositoryId: String, branchId: String, delaySeconds: Int, skip: Int, limit: Int): Promise<Array<HistoryIntervalJS>>
127+
fun getHistoryForFixedIntervalsForBranch(repositoryId: String, branchId: String, intervalDurationSeconds: Int, skip: Int, limit: Int): Promise<Array<HistoryIntervalJS>>
128+
fun getHistoryForProvidedIntervalsForBranch(repositoryId: String, branchId: String, splitAt: Array<Date>): Promise<Array<HistoryIntervalJS>>
118129

119130
/**
120131
* Fetch existing branches for a given repository from the model server.
@@ -208,8 +219,7 @@ internal class ClientJSImpl(private val modelClient: ModelClientV2) : ClientJS {
208219
RepositoryId(repositoryId),
209220
ObjectHash(headVersion),
210221
).range(
211-
skip = skip.toLong(),
212-
limit = limit.toLong(),
222+
pagination = PaginationParameters(skip, limit),
213223
)
214224
.map {
215225
VersionInformationJS(
@@ -221,6 +231,82 @@ internal class ClientJSImpl(private val modelClient: ModelClientV2) : ClientJS {
221231
.toTypedArray()
222232
}
223233

234+
override fun getHistorySessions(
235+
repositoryId: String,
236+
headVersion: String,
237+
delaySeconds: Int,
238+
skip: Int,
239+
limit: Int,
240+
): Promise<Array<HistoryIntervalJS>> = GlobalScope.promise {
241+
modelClient.queryHistory(
242+
RepositoryId(repositoryId),
243+
ObjectHash(headVersion),
244+
).sessions(
245+
delay = delaySeconds.seconds,
246+
pagination = PaginationParameters(skip, limit),
247+
).map { it.toJS() }.toTypedArray()
248+
}
249+
250+
override fun getHistoryForFixedIntervals(
251+
repositoryId: String,
252+
headVersion: String,
253+
intervalDurationSeconds: Int,
254+
skip: Int,
255+
limit: Int,
256+
): Promise<Array<HistoryIntervalJS>> = GlobalScope.promise {
257+
modelClient.queryHistory(
258+
RepositoryId(repositoryId),
259+
ObjectHash(headVersion),
260+
).intervals(
261+
interval = intervalDurationSeconds.seconds,
262+
pagination = PaginationParameters(skip, limit),
263+
).map { it.toJS() }.toTypedArray()
264+
}
265+
266+
override fun getHistoryForProvidedIntervals(
267+
repositoryId: String,
268+
headVersion: String,
269+
splitAt: Array<Date>,
270+
): Promise<Array<HistoryIntervalJS>> = GlobalScope.promise {
271+
modelClient.queryHistory(
272+
RepositoryId(repositoryId),
273+
ObjectHash(headVersion),
274+
).splitAt(
275+
splitPoints = splitAt.map { it.toKotlinInstant() },
276+
).map { it.toJS() }.toTypedArray()
277+
}
278+
279+
override fun getHistorySessionsForBranch(
280+
repositoryId: String,
281+
branchId: String,
282+
delaySeconds: Int,
283+
skip: Int,
284+
limit: Int,
285+
): Promise<Array<HistoryIntervalJS>> =
286+
GlobalScope.promise { modelClient.pullHash(RepositoryId(repositoryId).getBranchReference(branchId)) }
287+
.then { getHistorySessions(repositoryId, it, delaySeconds, skip, limit) }
288+
.then { it }
289+
290+
override fun getHistoryForFixedIntervalsForBranch(
291+
repositoryId: String,
292+
branchId: String,
293+
intervalDurationSeconds: Int,
294+
skip: Int,
295+
limit: Int,
296+
): Promise<Array<HistoryIntervalJS>> =
297+
GlobalScope.promise { modelClient.pullHash(RepositoryId(repositoryId).getBranchReference(branchId)) }
298+
.then { getHistoryForFixedIntervals(repositoryId, it, intervalDurationSeconds, skip, limit) }
299+
.then { it }
300+
301+
override fun getHistoryForProvidedIntervalsForBranch(
302+
repositoryId: String,
303+
branchId: String,
304+
splitAt: Array<Date>,
305+
): Promise<Array<HistoryIntervalJS>> =
306+
GlobalScope.promise { modelClient.pullHash(RepositoryId(repositoryId).getBranchReference(branchId)) }
307+
.then { getHistoryForProvidedIntervals(repositoryId, it, splitAt) }
308+
.then { it }
309+
224310
override fun dispose() {
225311
modelClient.close()
226312
}

model-client/src/jsMain/kotlin/org/modelix/model/client2/VersionInformationJS.kt

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package org.modelix.model.client2
22

3+
import kotlinx.datetime.toJSDate
4+
import org.modelix.datastructures.history.HistoryInterval
35
import kotlin.js.Date
46

57
/**
@@ -24,3 +26,25 @@ data class VersionInformationJS(
2426
*/
2527
val versionHash: String?,
2628
)
29+
30+
@JsExport
31+
class HistoryIntervalJS(
32+
val firstVersionHash: String,
33+
val lastVersionHash: String,
34+
/**
35+
* Number of versions contained in this interval.
36+
*/
37+
val size: Int,
38+
val minTime: Date,
39+
val maxTime: Date,
40+
val authors: Array<String>,
41+
)
42+
43+
fun HistoryInterval.toJS() = HistoryIntervalJS(
44+
firstVersionHash = firstVersionHash.toString(),
45+
lastVersionHash = lastVersionHash.toString(),
46+
size = size.toInt(),
47+
minTime = minTime.toJSDate(),
48+
maxTime = maxTime.toJSDate(),
49+
authors = authors.toTypedArray(),
50+
)

model-datastructure/src/commonMain/kotlin/org/modelix/datastructures/history/HistoryQueries.kt

Lines changed: 67 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -17,79 +17,88 @@ class HistoryQueries(val historyIndex: suspend () -> Object<HistoryIndexNode>) :
1717
override suspend fun sessions(
1818
timeRange: ClosedRange<Instant>?,
1919
delay: Duration,
20+
pagination: PaginationParameters,
2021
): List<HistoryInterval> {
2122
val index: Object<HistoryIndexNode> = historyIndex()
2223
val sessions = ArrayList<HistoryInterval>()
23-
var previousMinTime = Instant.Companion.fromEpochSeconds(Long.MAX_VALUE)
24+
var previousMinTime = Instant.fromEpochSeconds(Long.MAX_VALUE)
2425

2526
// In the worst case two adjacent intervals contain a single entry directly at the border.
2627
// The maximum difference between these two entries is less than two times the interval.
2728
val interval = delay / 2
2829

29-
index.data.splitAtInterval(EquidistantIntervalsSpec(interval).withTimeRangeFilter(timeRange)).iterateSuspending(index.graph) {
30-
if (previousMinTime - it.maxTime >= delay) {
31-
sessions += HistoryInterval(
32-
firstVersionHash = it.firstVersion.getHash(),
33-
lastVersionHash = it.lastVersion.getHash(),
34-
size = it.size,
35-
minTime = it.minTime,
36-
maxTime = it.maxTime,
37-
authors = it.authors,
38-
)
39-
} else {
40-
val entry = sessions[sessions.lastIndex]
41-
sessions[sessions.lastIndex] = HistoryInterval(
42-
firstVersionHash = it.firstVersion.getHash(),
43-
lastVersionHash = entry.lastVersionHash,
44-
size = entry.size + it.size,
45-
minTime = minOf(entry.minTime, it.minTime),
46-
maxTime = maxOf(entry.maxTime, it.maxTime),
47-
authors = entry.authors + it.authors,
48-
)
30+
runUntilLimit {
31+
index.data.splitAtInterval(EquidistantIntervalsSpec(interval).withTimeRangeFilter(timeRange)).iterateSuspending(index.graph) {
32+
if (previousMinTime - it.maxTime >= delay) {
33+
if (sessions.lastIndex >= pagination.asRange().last) throw LimitReached()
34+
sessions += HistoryInterval(
35+
firstVersionHash = it.firstVersion.getHash(),
36+
lastVersionHash = it.lastVersion.getHash(),
37+
size = it.size,
38+
minTime = it.minTime,
39+
maxTime = it.maxTime,
40+
authors = it.authors,
41+
)
42+
} else {
43+
val entry = sessions[sessions.lastIndex]
44+
sessions[sessions.lastIndex] = HistoryInterval(
45+
firstVersionHash = it.firstVersion.getHash(),
46+
lastVersionHash = entry.lastVersionHash,
47+
size = entry.size + it.size,
48+
minTime = minOf(entry.minTime, it.minTime),
49+
maxTime = maxOf(entry.maxTime, it.maxTime),
50+
authors = entry.authors + it.authors,
51+
)
52+
}
53+
previousMinTime = it.minTime
4954
}
50-
previousMinTime = it.minTime
5155
}
5256

53-
return sessions
57+
return pagination.apply(sessions)
5458
}
5559

5660
override suspend fun intervals(
5761
timeRange: ClosedRange<Instant>?,
5862
interval: Duration,
63+
pagination: PaginationParameters,
5964
): List<HistoryInterval> {
60-
return intervals(EquidistantIntervalsSpec(interval).withTimeRangeFilter(timeRange))
65+
return intervals(EquidistantIntervalsSpec(interval).withTimeRangeFilter(timeRange), pagination)
6166
}
6267

6368
suspend fun intervals(
6469
intervalsSpec: IntervalsSpec,
70+
pagination: PaginationParameters,
6571
): List<HistoryInterval> {
6672
val index: Object<HistoryIndexNode> = historyIndex()
6773
val mergedEntries = ArrayList<HistoryInterval>()
6874
var previousIntervalId: Long = Long.MAX_VALUE
6975

70-
index.data.splitAtInterval(intervalsSpec).iterateSuspending(index.graph) {
71-
val intervalId = intervalsSpec.getIntervalIndex(it.maxTime)
72-
check(intervalId <= previousIntervalId)
73-
if (intervalId == previousIntervalId) {
74-
val entry = mergedEntries[mergedEntries.lastIndex]
75-
mergedEntries[mergedEntries.lastIndex] = HistoryInterval(
76-
firstVersionHash = it.firstVersion.getHash(),
77-
lastVersionHash = entry.lastVersionHash,
78-
size = entry.size + it.size,
79-
minTime = minOf(entry.minTime, it.minTime),
80-
maxTime = maxOf(entry.maxTime, it.maxTime),
81-
authors = entry.authors + it.authors,
82-
)
83-
} else {
84-
previousIntervalId = intervalId
85-
mergedEntries += HistoryInterval(
86-
firstVersionHash = it.firstVersion.getHash(),
87-
lastVersionHash = it.lastVersion.getHash(),
88-
size = it.size,
89-
minTime = it.minTime,
90-
maxTime = it.maxTime,
91-
authors = it.authors,
92-
)
76+
runUntilLimit {
77+
index.data.splitAtInterval(intervalsSpec).iterateSuspending(index.graph) {
78+
val intervalId = intervalsSpec.getIntervalIndex(it.maxTime)
79+
check(intervalId <= previousIntervalId)
80+
if (intervalId == previousIntervalId) {
81+
val entry = mergedEntries[mergedEntries.lastIndex]
82+
mergedEntries[mergedEntries.lastIndex] = HistoryInterval(
83+
firstVersionHash = it.firstVersion.getHash(),
84+
lastVersionHash = entry.lastVersionHash,
85+
size = entry.size + it.size,
86+
minTime = minOf(entry.minTime, it.minTime),
87+
maxTime = maxOf(entry.maxTime, it.maxTime),
88+
authors = entry.authors + it.authors,
89+
)
90+
} else {
91+
if (mergedEntries.lastIndex >= pagination.asRange().last) throw LimitReached()
92+
previousIntervalId = intervalId
93+
mergedEntries += HistoryInterval(
94+
firstVersionHash = it.firstVersion.getHash(),
95+
lastVersionHash = it.lastVersion.getHash(),
96+
size = it.size,
97+
minTime = it.minTime,
98+
maxTime = it.maxTime,
99+
authors = it.authors,
100+
)
101+
}
93102
}
94103
}
95104

@@ -98,13 +107,12 @@ class HistoryQueries(val historyIndex: suspend () -> Object<HistoryIndexNode>) :
98107

99108
override suspend fun range(
100109
timeRange: ClosedRange<Instant>?,
101-
skip: Long,
102-
limit: Long,
110+
pagination: PaginationParameters,
103111
): List<HistoryEntry> {
104112
val index: Object<HistoryIndexNode> = historyIndex()
105113
val inTimeRange = if (timeRange == null) index else index.getRange(timeRange).orNull().getSuspending(index.graph)
106114
if (inTimeRange == null) return emptyList()
107-
return inTimeRange.data.getRange(skip until (limit + skip))
115+
return inTimeRange.data.getRange(pagination.asRange())
108116
.flatMapOrdered { it.getAllVersionsReversed() }
109117
.flatMapOrdered { it.resolve() }
110118
.map {
@@ -120,6 +128,14 @@ class HistoryQueries(val historyIndex: suspend () -> Object<HistoryIndexNode>) :
120128
}
121129

122130
override suspend fun splitAt(splitPoints: List<Instant>): List<HistoryInterval> {
123-
return intervals(SplitPointsIntervalSpec(splitPoints))
131+
return intervals(SplitPointsIntervalSpec(splitPoints), PaginationParameters.ALL)
124132
}
125133
}
134+
135+
private class LimitReached : RuntimeException("limit reached")
136+
137+
private inline fun runUntilLimit(body: () -> Unit) {
138+
try {
139+
body()
140+
} catch (ex: LimitReached) {}
141+
}

0 commit comments

Comments
 (0)