velocious 1.0.513 → 1.0.514

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.
@@ -32,15 +32,19 @@ export default class SyncModelChangeFeedService {
32
32
  }
33
33
 
34
34
  /**
35
- * Builds a stable change-feed page.
36
- * @returns {Promise<{status: string, nextCursor: {id: string, serverSequence: number, updatedAt: string} | null, syncs: Array<Record<string, unknown>>, upToCursor: {id: string, serverSequence: number, updatedAt: string} | null}>} Change-feed page result.
35
+ * Builds a stable change-feed page. The additive `total` is the scope's pending
36
+ * change count from the request cursor to the snapshot bound (a COUNT, not a
37
+ * materialized read), so a client can render a "synced of total" progress bar;
38
+ * older clients ignore it.
39
+ * @returns {Promise<{status: string, nextCursor: {id: string, serverSequence: number, updatedAt: string} | null, syncs: Array<Record<string, unknown>>, total: number, upToCursor: {id: string, serverSequence: number, updatedAt: string} | null}>} Change-feed page result.
37
40
  */
38
41
  async changes() {
39
42
  const limit = this.normalizedLimit(this.params.limit)
40
43
  const upToCursor = await this.resolveUpToCursor()
41
44
 
42
- if (!upToCursor) return {status: "success", nextCursor: null, syncs: [], upToCursor: null}
45
+ if (!upToCursor) return {status: "success", nextCursor: null, syncs: [], total: 0, upToCursor: null}
43
46
 
47
+ const total = await this.totalPendingChanges({upToCursor})
44
48
  const query = this.pageQuery({limit, upToCursor})
45
49
  const records = await query.toArray()
46
50
  const nextCursor = records.length > 0 ? this.cursorForRecord(records[records.length - 1]) : upToCursor
@@ -49,6 +53,7 @@ export default class SyncModelChangeFeedService {
49
53
  status: "success",
50
54
  nextCursor,
51
55
  syncs: records.map((record) => this.serializeRecord(record)),
56
+ total,
52
57
  upToCursor
53
58
  }
54
59
  }
@@ -124,6 +129,34 @@ export default class SyncModelChangeFeedService {
124
129
  * @returns {import("../database/query/model-class-query.js").default} Page query.
125
130
  */
126
131
  pageQuery({limit, upToCursor}) {
132
+ const query = this.cursorFilteredQuery({upToCursor})
133
+ const driver = query.driver
134
+ const serverSequenceColumn = `${driver.quoteTable(this.modelClass.tableName())}.${driver.quoteColumn("server_sequence")}`
135
+
136
+ return query
137
+ .order(`${serverSequenceColumn} ASC`)
138
+ .limit(limit)
139
+ }
140
+
141
+ /**
142
+ * Counts the scope's pending change rows from the request cursor to the snapshot
143
+ * bound without materializing them, so the client can render a stable "of Y"
144
+ * progress denominator.
145
+ * @param {{upToCursor: {id: string, serverSequence: number, updatedAt: string}}} args - Count args.
146
+ * @returns {Promise<number>} Pending change count from the request cursor.
147
+ */
148
+ async totalPendingChanges({upToCursor}) {
149
+ return await this.cursorFilteredQuery({upToCursor}).count()
150
+ }
151
+
152
+ /**
153
+ * Builds a scoped query with the snapshot upper bound and after-cursor filters
154
+ * applied, but without ordering or a page limit. Shared by the page read and the
155
+ * total-count so both count the same set of rows.
156
+ * @param {{upToCursor: {id: string, serverSequence: number, updatedAt: string}}} args - Filter args.
157
+ * @returns {import("../database/query/model-class-query.js").default} Cursor-filtered query.
158
+ */
159
+ cursorFilteredQuery({upToCursor}) {
127
160
  const query = this.scopedQuery()
128
161
  const driver = query.driver
129
162
  const table = driver.quoteTable(this.modelClass.tableName())
@@ -131,10 +164,7 @@ export default class SyncModelChangeFeedService {
131
164
  const updatedAtColumn = `${table}.${driver.quoteColumn("updated_at")}`
132
165
  const idColumn = `${table}.${driver.quoteColumn("id")}`
133
166
 
134
- query
135
- .where(`${serverSequenceColumn} <= ${driver.quote(upToCursor.serverSequence)}`)
136
- .order(`${serverSequenceColumn} ASC`)
137
- .limit(limit)
167
+ query.where(`${serverSequenceColumn} <= ${driver.quote(upToCursor.serverSequence)}`)
138
168
 
139
169
  const afterServerSequence = this.optionalPositiveIntegerParam(this.params.afterServerSequence, "afterServerSequence")
140
170