velocious 1.0.443 → 1.0.444
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/build/database/record/acts-as-list.js +89 -24
- package/build/database/record/index.js +12 -1
- package/build/src/database/record/acts-as-list.d.ts.map +1 -1
- package/build/src/database/record/acts-as-list.js +80 -23
- package/build/src/database/record/index.d.ts +7 -2
- package/build/src/database/record/index.d.ts.map +1 -1
- package/build/src/database/record/index.js +11 -2
- package/package.json +1 -1
- package/src/database/record/acts-as-list.js +89 -24
- package/src/database/record/index.js +12 -1
|
@@ -67,14 +67,15 @@ export default function registerActsAsListCallbacks(modelClass, positionColumn,
|
|
|
67
67
|
@type {typeof import("./index.js").default} */ (record.constructor)
|
|
68
68
|
const posColumn = modelClass.getColumnNameForAttributeName(positionColumn)
|
|
69
69
|
const scopeCol = modelClass.getColumnNameForAttributeName(scope)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const changes =
|
|
74
|
-
|
|
75
|
-
|
|
70
|
+
/** @type {Record<string, ?>} */
|
|
71
|
+
const rawAttributes = record._attributes || {}
|
|
72
|
+
/** @type {Record<string, ?>} */
|
|
73
|
+
const changes = record._changes || {}
|
|
74
|
+
/** @type {Set<string>} */
|
|
75
|
+
const assignedAttributeNames = record._assignedAttributeNames || new Set()
|
|
76
76
|
const posChanged = posColumn in changes
|
|
77
77
|
const scopeChanged = scopeCol in changes
|
|
78
|
+
const posAssigned = assignedAttributeNames.has(positionColumn)
|
|
78
79
|
|
|
79
80
|
if (!posChanged && !scopeChanged) return
|
|
80
81
|
|
|
@@ -102,18 +103,13 @@ export default function registerActsAsListCallbacks(modelClass, positionColumn,
|
|
|
102
103
|
if (oldPosition == null || newPosition == null) return
|
|
103
104
|
if (newPosition === oldPosition && newScopeValue === oldScopeValue) return
|
|
104
105
|
|
|
105
|
-
// Move the record out of the way before shifting others to avoid
|
|
106
|
-
// intermediate UNIQUE constraint violations. Use the old scope value
|
|
107
|
-
// for the move-out-of-way because the record is still in the old scope.
|
|
108
|
-
await moveOutOfWay({record, positionColumn, scope, scopeValue: oldScopeValue})
|
|
109
|
-
setShiftingFlag(record, false)
|
|
110
|
-
|
|
111
106
|
if (scopeChanged && oldScopeValue !== newScopeValue) {
|
|
112
|
-
let targetPosition = newPosition
|
|
113
|
-
|
|
114
107
|
// When only the scope changes without a new position, append to the end
|
|
115
108
|
// of the new scope. There is no target-scope row to shift out of the way.
|
|
116
|
-
if (!
|
|
109
|
+
if (!posAssigned) {
|
|
110
|
+
await moveOutOfWay({record, positionColumn, scope, scopeValue: oldScopeValue})
|
|
111
|
+
setShiftingFlag(record, false)
|
|
112
|
+
|
|
117
113
|
const highestNew = await highestPositionInScope({record, positionColumn, scope, scopeValue: newScopeValue})
|
|
118
114
|
const nextPos = highestNew + 1
|
|
119
115
|
|
|
@@ -122,11 +118,17 @@ export default function registerActsAsListCallbacks(modelClass, positionColumn,
|
|
|
122
118
|
return
|
|
123
119
|
}
|
|
124
120
|
|
|
121
|
+
await moveOutOfWay({record, positionColumn, scope, scopeValue: oldScopeValue, targetScopeValue: newScopeValue})
|
|
122
|
+
setShiftingFlag(record, false)
|
|
125
123
|
await shiftPositionsDown({record, positionColumn, scope, scopeValue: oldScopeValue, fromPosition: oldPosition + 1})
|
|
126
|
-
await shiftPositionsUp({record, positionColumn, scope, scopeValue: newScopeValue, fromPosition:
|
|
124
|
+
await shiftPositionsUp({record, positionColumn, scope, scopeValue: newScopeValue, fromPosition: newPosition, excludeRecordId: record.id()})
|
|
125
|
+
await placeMovedRecord({record, positionColumn, scope, scopeValue: newScopeValue, position: newPosition})
|
|
127
126
|
return
|
|
128
127
|
}
|
|
129
128
|
|
|
129
|
+
await moveOutOfWay({record, positionColumn, scope, scopeValue: oldScopeValue})
|
|
130
|
+
setShiftingFlag(record, false)
|
|
131
|
+
|
|
130
132
|
if (newPosition < oldPosition) {
|
|
131
133
|
await shiftPositionsUp({record, positionColumn, scope, fromPosition: newPosition, toPosition: oldPosition})
|
|
132
134
|
} else if (newPosition > oldPosition) {
|
|
@@ -146,6 +148,53 @@ export default function registerActsAsListCallbacks(modelClass, positionColumn,
|
|
|
146
148
|
})
|
|
147
149
|
}
|
|
148
150
|
|
|
151
|
+
/**
|
|
152
|
+
* Places a moved row after surrounding rows have shifted.
|
|
153
|
+
* @param {object} args - Arguments.
|
|
154
|
+
* @param {import("./index.js").default} args.record - Model instance.
|
|
155
|
+
* @param {string} args.positionColumn - Position attribute name.
|
|
156
|
+
* @param {string} args.scope - Scope attribute name.
|
|
157
|
+
* @param {string | number} args.scopeValue - Destination scope value.
|
|
158
|
+
* @param {number} args.position - Destination position.
|
|
159
|
+
* @returns {Promise<void>} Resolves after placement.
|
|
160
|
+
*/
|
|
161
|
+
async function placeMovedRecord({record, positionColumn, scope, scopeValue, position}) {
|
|
162
|
+
const modelClass = /** @type {typeof import("./index.js").default} */ (record.constructor)
|
|
163
|
+
const connection = modelClass.connection()
|
|
164
|
+
const tableSql = connection.quoteTable(modelClass._getTable().getName())
|
|
165
|
+
const scopeCol = modelClass.getColumnNameForAttributeName(scope)
|
|
166
|
+
const posCol = modelClass.getColumnNameForAttributeName(positionColumn)
|
|
167
|
+
const preservedChanges = {...record._changes}
|
|
168
|
+
const scopeColumnSql = connection.quoteColumn(scopeCol)
|
|
169
|
+
const positionColumnSql = connection.quoteColumn(posCol)
|
|
170
|
+
const primaryKeySql = connection.quoteColumn(modelClass.primaryKey())
|
|
171
|
+
|
|
172
|
+
delete preservedChanges[scopeCol]
|
|
173
|
+
delete preservedChanges[posCol]
|
|
174
|
+
|
|
175
|
+
await connection.query(
|
|
176
|
+
`UPDATE ${tableSql} SET ${scopeColumnSql} = ${connection.quote(scopeValue)}, ${positionColumnSql} = ${connection.quote(position)} WHERE ${primaryKeySql} = ${connection.quote(record.id())}`
|
|
177
|
+
)
|
|
178
|
+
await record._reloadWithId(record.id())
|
|
179
|
+
record._changes = preservedChanges
|
|
180
|
+
clearBelongsToChangeForScope(record)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Clears dirty belongs-to state for the scope FK after direct placement.
|
|
185
|
+
* @param {import("./index.js").default} record - Model instance.
|
|
186
|
+
* @returns {void} Nothing.
|
|
187
|
+
*/
|
|
188
|
+
function clearBelongsToChangeForScope(record) {
|
|
189
|
+
for (const relationshipName in record._instanceRelationships || {}) {
|
|
190
|
+
const relationship = record._instanceRelationships[relationshipName]
|
|
191
|
+
|
|
192
|
+
if (relationship.getType() !== "belongsTo") continue
|
|
193
|
+
|
|
194
|
+
relationship.setDirty(false)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
149
198
|
/**
|
|
150
199
|
* Bumps positions UP by 1 in the range [fromPosition, toPosition) within the
|
|
151
200
|
* same scope. Updates in descending order to avoid intermediate UNIQUE
|
|
@@ -157,9 +206,10 @@ export default function registerActsAsListCallbacks(modelClass, positionColumn,
|
|
|
157
206
|
* @param {number} args.fromPosition - Starting position (inclusive).
|
|
158
207
|
* @param {number} [args.toPosition] - Ending position (exclusive).
|
|
159
208
|
* @param {string | number} [args.scopeValue] - Explicit scope value.
|
|
209
|
+
* @param {string | number} [args.excludeRecordId] - Record id to exclude from shifts.
|
|
160
210
|
* @returns {Promise<void>}
|
|
161
211
|
*/
|
|
162
|
-
async function shiftPositionsUp({record, positionColumn, scope, fromPosition, toPosition, scopeValue}) {
|
|
212
|
+
async function shiftPositionsUp({record, positionColumn, scope, fromPosition, toPosition, scopeValue, excludeRecordId}) {
|
|
163
213
|
const modelClass = /**
|
|
164
214
|
* Narrows the runtime value to the documented type.
|
|
165
215
|
@type {typeof import("./index.js").default} */ (record.constructor)
|
|
@@ -173,16 +223,25 @@ async function shiftPositionsUp({record, positionColumn, scope, fromPosition, to
|
|
|
173
223
|
const positionColumnName = modelClass.getColumnNameForAttributeName(positionColumn)
|
|
174
224
|
const positionColumnSql = connection.quoteColumn(positionColumnName)
|
|
175
225
|
const scopeColumnSql = connection.quoteColumn(scopeColumnName)
|
|
226
|
+
const primaryKeySql = connection.quoteColumn(modelClass.primaryKey())
|
|
176
227
|
const tableSql = connection.quoteTable(tableName)
|
|
177
228
|
const quotedScope = connection.quote(resolvedScopeValue)
|
|
178
229
|
|
|
179
230
|
// Load rows in descending order so we bump the highest first
|
|
180
231
|
let query = modelClass
|
|
232
|
+
.select(modelClass.primaryKey())
|
|
181
233
|
.select(positionColumn)
|
|
182
234
|
.where({[scopeColumnName]: resolvedScopeValue})
|
|
183
235
|
.where(`${positionColumnSql} >= ${connection.quote(fromPosition)}`)
|
|
236
|
+
.where(`${positionColumnSql} > 0`)
|
|
184
237
|
.order(`${positionColumnSql} DESC`)
|
|
185
238
|
|
|
239
|
+
const recordIdToExclude = excludeRecordId || (record.isPersisted() ? record.id() : null)
|
|
240
|
+
|
|
241
|
+
if (recordIdToExclude != null) {
|
|
242
|
+
query = query.where(`${primaryKeySql} != ${connection.quote(recordIdToExclude)}`)
|
|
243
|
+
}
|
|
244
|
+
|
|
186
245
|
if (toPosition != null) {
|
|
187
246
|
query = query.where(`${positionColumnSql} < ${connection.quote(toPosition)}`)
|
|
188
247
|
}
|
|
@@ -196,7 +255,7 @@ async function shiftPositionsUp({record, positionColumn, scope, fromPosition, to
|
|
|
196
255
|
const currentPos = Number(row.readAttribute(positionColumn))
|
|
197
256
|
|
|
198
257
|
await connection.query(
|
|
199
|
-
`UPDATE ${tableSql} SET ${positionColumnSql} = ${positionColumnSql} + 1 WHERE ${scopeColumnSql} = ${quotedScope} AND ${positionColumnSql} = ${connection.quote(currentPos)}`
|
|
258
|
+
`UPDATE ${tableSql} SET ${positionColumnSql} = ${positionColumnSql} + 1 WHERE ${primaryKeySql} = ${connection.quote(row.id())} AND ${scopeColumnSql} = ${quotedScope} AND ${positionColumnSql} = ${connection.quote(currentPos)}`
|
|
200
259
|
)
|
|
201
260
|
}
|
|
202
261
|
} finally {
|
|
@@ -231,14 +290,18 @@ async function shiftPositionsDown({record, positionColumn, scope, fromPosition,
|
|
|
231
290
|
const positionColumnName = modelClass.getColumnNameForAttributeName(positionColumn)
|
|
232
291
|
const positionColumnSql = connection.quoteColumn(positionColumnName)
|
|
233
292
|
const scopeColumnSql = connection.quoteColumn(scopeColumnName)
|
|
293
|
+
const primaryKeySql = connection.quoteColumn(modelClass.primaryKey())
|
|
234
294
|
const tableSql = connection.quoteTable(tableName)
|
|
235
295
|
const quotedScope = connection.quote(resolvedScopeValue)
|
|
236
296
|
|
|
237
297
|
// Load rows in ascending order so we shift the lowest gap first
|
|
238
298
|
let query = modelClass
|
|
299
|
+
.select(modelClass.primaryKey())
|
|
239
300
|
.select(positionColumn)
|
|
240
301
|
.where({[scopeColumnName]: resolvedScopeValue})
|
|
241
302
|
.where(`${positionColumnSql} >= ${connection.quote(fromPosition)}`)
|
|
303
|
+
.where(`${positionColumnSql} > 0`)
|
|
304
|
+
.where(`${primaryKeySql} != ${connection.quote(record.id())}`)
|
|
242
305
|
.order({column: positionColumnName, direction: "ASC"})
|
|
243
306
|
|
|
244
307
|
if (toPosition != null) {
|
|
@@ -254,7 +317,7 @@ async function shiftPositionsDown({record, positionColumn, scope, fromPosition,
|
|
|
254
317
|
const currentPos = Number(row.readAttribute(positionColumn))
|
|
255
318
|
|
|
256
319
|
await connection.query(
|
|
257
|
-
`UPDATE ${tableSql} SET ${positionColumnSql} = ${positionColumnSql} - 1 WHERE ${scopeColumnSql} = ${quotedScope} AND ${positionColumnSql} = ${connection.quote(currentPos)}`
|
|
320
|
+
`UPDATE ${tableSql} SET ${positionColumnSql} = ${positionColumnSql} - 1 WHERE ${primaryKeySql} = ${connection.quote(row.id())} AND ${scopeColumnSql} = ${quotedScope} AND ${positionColumnSql} = ${connection.quote(currentPos)}`
|
|
258
321
|
)
|
|
259
322
|
}
|
|
260
323
|
} finally {
|
|
@@ -342,21 +405,23 @@ function resolveScopeValue(record, scope) {
|
|
|
342
405
|
* @param {import("./index.js").default} args.record - Model instance.
|
|
343
406
|
* @param {string} args.positionColumn - camelCase position attribute.
|
|
344
407
|
* @param {string} args.scope - camelCase scope attribute.
|
|
345
|
-
* @param {string | number | null} [args.scopeValue] -
|
|
408
|
+
* @param {string | number | null} [args.scopeValue] - Scope containing the record before move-out.
|
|
409
|
+
* @param {string | number | null} [args.targetScopeValue] - Temporary scope value to assign.
|
|
346
410
|
* @returns {Promise<void>}
|
|
347
411
|
*/
|
|
348
|
-
async function moveOutOfWay({record, positionColumn, scope, scopeValue}) {
|
|
412
|
+
async function moveOutOfWay({record, positionColumn, scope, scopeValue, targetScopeValue}) {
|
|
349
413
|
const modelClass = /**
|
|
350
414
|
* Narrows the runtime value to the documented type.
|
|
351
415
|
@type {typeof import("./index.js").default} */ (record.constructor)
|
|
352
416
|
const connection = modelClass.connection()
|
|
353
417
|
const tableName = modelClass._getTable().getName()
|
|
354
418
|
const resolvedScopeValue = scopeValue != null ? scopeValue : resolveScopeValue(record, scope)
|
|
419
|
+
const resolvedTargetScopeValue = targetScopeValue != null ? targetScopeValue : resolvedScopeValue
|
|
355
420
|
|
|
356
421
|
if (resolvedScopeValue == null) return
|
|
422
|
+
if (resolvedTargetScopeValue == null) return
|
|
357
423
|
|
|
358
|
-
const
|
|
359
|
-
const tempPosition = highest + 10000
|
|
424
|
+
const tempPosition = -record.id()
|
|
360
425
|
const positionColumnSql = connection.quoteColumn(modelClass.getColumnNameForAttributeName(positionColumn))
|
|
361
426
|
const scopeColumnSql = connection.quoteColumn(modelClass.getColumnNameForAttributeName(scope))
|
|
362
427
|
const tableSql = connection.quoteTable(tableName)
|
|
@@ -366,7 +431,7 @@ async function moveOutOfWay({record, positionColumn, scope, scopeValue}) {
|
|
|
366
431
|
|
|
367
432
|
try {
|
|
368
433
|
await connection.query(
|
|
369
|
-
`UPDATE ${tableSql} SET ${positionColumnSql} = ${connection.quote(tempPosition)} WHERE ${scopeColumnSql} = ${connection.quote(resolvedScopeValue)} AND ${pkSql} = ${connection.quote(record.id())}`
|
|
434
|
+
`UPDATE ${tableSql} SET ${scopeColumnSql} = ${connection.quote(resolvedTargetScopeValue)}, ${positionColumnSql} = ${connection.quote(tempPosition)} WHERE ${scopeColumnSql} = ${connection.quote(resolvedScopeValue)} AND ${pkSql} = ${connection.quote(record.id())}`
|
|
370
435
|
)
|
|
371
436
|
} finally {
|
|
372
437
|
// Don't clear the flag here — the caller will do that after shifts
|
|
@@ -435,6 +435,12 @@ class VelociousDatabaseRecord {
|
|
|
435
435
|
@type {Record<string, ?>} */
|
|
436
436
|
_changes = {}
|
|
437
437
|
|
|
438
|
+
/**
|
|
439
|
+
* Attribute names explicitly assigned in the current update call.
|
|
440
|
+
@type {Set<string> | undefined}
|
|
441
|
+
*/
|
|
442
|
+
_assignedAttributeNames = undefined
|
|
443
|
+
|
|
438
444
|
/**
|
|
439
445
|
* Columns as hash.
|
|
440
446
|
@type {Record<string, import("../drivers/base-column.js").default>} */
|
|
@@ -2414,6 +2420,8 @@ class VelociousDatabaseRecord {
|
|
|
2414
2420
|
})
|
|
2415
2421
|
})
|
|
2416
2422
|
|
|
2423
|
+
this._assignedAttributeNames = undefined
|
|
2424
|
+
|
|
2417
2425
|
return result
|
|
2418
2426
|
}
|
|
2419
2427
|
|
|
@@ -2512,7 +2520,7 @@ class VelociousDatabaseRecord {
|
|
|
2512
2520
|
|
|
2513
2521
|
/**
|
|
2514
2522
|
* Resolves a relationship foreign-key column to this model's public attribute name.
|
|
2515
|
-
* @param {import("./instance-relationships/base.js").default
|
|
2523
|
+
* @param {import("./instance-relationships/base.js").default<typeof VelociousDatabaseRecord, typeof VelociousDatabaseRecord>} instanceRelationship - Relationship instance.
|
|
2516
2524
|
* @returns {string} Attribute name accepted by setAttribute/assign.
|
|
2517
2525
|
*/
|
|
2518
2526
|
_relationshipForeignKeyAttribute(instanceRelationship) {
|
|
@@ -3374,7 +3382,9 @@ class VelociousDatabaseRecord {
|
|
|
3374
3382
|
* @returns {void} - No return value.
|
|
3375
3383
|
*/
|
|
3376
3384
|
assign(attributesToAssign) {
|
|
3385
|
+
this._assignedAttributeNames ||= new Set()
|
|
3377
3386
|
for (const attributeToAssign in attributesToAssign) {
|
|
3387
|
+
this._assignedAttributeNames.add(attributeToAssign)
|
|
3378
3388
|
this.setAttribute(attributeToAssign, attributesToAssign[attributeToAssign])
|
|
3379
3389
|
}
|
|
3380
3390
|
}
|
|
@@ -4178,6 +4188,7 @@ class VelociousDatabaseRecord {
|
|
|
4178
4188
|
|
|
4179
4189
|
this._attributes = reloadedModel.rawAttributes()
|
|
4180
4190
|
this._changes = {}
|
|
4191
|
+
this._assignedAttributeNames = undefined
|
|
4181
4192
|
}
|
|
4182
4193
|
|
|
4183
4194
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"acts-as-list.d.ts","sourceRoot":"","sources":["../../../../src/database/record/acts-as-list.js"],"names":[],"mappings":"AA8BA;;;;;;;;;;;;;;GAcG;AACH,gEANW,cAAc,YAAY,EAAE,OAAO,kBACnC,MAAM,aAEd;IAAwB,KAAK,EAArB,MAAM;CACd,GAAU,IAAI,
|
|
1
|
+
{"version":3,"file":"acts-as-list.d.ts","sourceRoot":"","sources":["../../../../src/database/record/acts-as-list.js"],"names":[],"mappings":"AA8BA;;;;;;;;;;;;;;GAcG;AACH,gEANW,cAAc,YAAY,EAAE,OAAO,kBACnC,MAAM,aAEd;IAAwB,KAAK,EAArB,MAAM;CACd,GAAU,IAAI,CAyGhB"}
|