velocious 1.0.498 → 1.0.499

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.
@@ -0,0 +1,240 @@
1
+ // @ts-check
2
+
3
+ /**
4
+ * Reactive counter-cache driven by a per-record magnitude.
5
+ *
6
+ * Maintains a counter column on a `belongsTo` parent as the running sum of each
7
+ * child's magnitude — a small number derived from one source attribute (e.g.
8
+ * `1` while a build's `status` is `running`, else `0`). On every create, update
9
+ * and destroy the change in magnitude is applied to the parent as a single
10
+ * atomic increment (`SET col = col + delta`), and when the foreign key changes
11
+ * the magnitude is moved from the old parent to the new one.
12
+ *
13
+ * Because the counter is derived from the source attribute and diffed on every
14
+ * write, it follows that attribute automatically no matter which code path wrote
15
+ * it — there is no per-transition increment/decrement to forget. The old value is
16
+ * captured in `beforeSave` (Velocious clears `changes()` during the post-update
17
+ * reload) and consumed in `afterSave`, so the increment commits atomically with
18
+ * the row it reflects.
19
+ * @typedef {{
20
+ * belongsTo: string,
21
+ * counterColumn: string,
22
+ * sourceAttribute: string,
23
+ * magnitude: (sourceValue: ?) => number
24
+ * }} MagnitudeCounterCacheDefinition
25
+ */
26
+
27
+ /**
28
+ * Captured pending magnitude change stashed on a record between beforeSave and afterSave.
29
+ * @typedef {{newMagnitude: number, oldMagnitude: number, newParentId: ?, oldParentId: ?}} PendingMagnitudeDelta
30
+ */
31
+
32
+ /**
33
+ * Registers a reactive magnitude counter-cache on a model class.
34
+ * @param {typeof import("./index.js").default} modelClass - Model class to add the counter cache to.
35
+ * @param {MagnitudeCounterCacheDefinition} definition - Counter cache definition.
36
+ * @returns {void}
37
+ */
38
+ export function registerMagnitudeCounterCache(modelClass, definition) {
39
+ /**
40
+ * Dynamic class.
41
+ * @type {?} */
42
+ const dynamicClass = modelClass
43
+ const registeredFlag = `_magnitudeCounterCacheRegistered_${definition.counterColumn}`
44
+
45
+ // Idempotent per counter column: re-declaring (or subclassing) must not double-register.
46
+ if (Object.prototype.hasOwnProperty.call(dynamicClass, registeredFlag) && dynamicClass[registeredFlag]) {
47
+ return
48
+ }
49
+
50
+ dynamicClass[registeredFlag] = true
51
+
52
+ const pendingKey = `_magnitudeCounterCachePending_${definition.counterColumn}`
53
+
54
+ modelClass.beforeSave(async function (record) {
55
+ /**
56
+ * Dynamic record.
57
+ * @type {?} */
58
+ const dynamicRecord = record
59
+
60
+ dynamicRecord[pendingKey] = computePendingMagnitudeDelta(record, definition)
61
+ })
62
+
63
+ modelClass.afterSave(async function (record) {
64
+ /**
65
+ * Dynamic record.
66
+ * @type {?} */
67
+ const dynamicRecord = record
68
+ const pending = /** @type {PendingMagnitudeDelta | null} */ (dynamicRecord[pendingKey])
69
+
70
+ dynamicRecord[pendingKey] = null
71
+
72
+ if (pending) {
73
+ await applyPendingMagnitudeDelta(record, definition, pending)
74
+ }
75
+ })
76
+
77
+ modelClass.afterDestroy(async function (record) {
78
+ const magnitude = definition.magnitude(record.readAttribute(definition.sourceAttribute))
79
+ const parentId = currentParentId(record, definition)
80
+
81
+ if (parentId && magnitude !== 0) {
82
+ await incrementParentCounter(record, definition, parentId, -magnitude)
83
+ }
84
+ })
85
+ }
86
+
87
+ /**
88
+ * Diffs the source attribute (and foreign key) across the pending save to capture
89
+ * how the parent counter should move. Runs in beforeSave, where `changes()` still
90
+ * holds the pre-save values.
91
+ * @param {import("./index.js").default} record - Record being saved.
92
+ * @param {MagnitudeCounterCacheDefinition} definition - Counter cache definition.
93
+ * @returns {PendingMagnitudeDelta} The captured magnitude change.
94
+ */
95
+ function computePendingMagnitudeDelta(record, definition) {
96
+ const changes = record.changes()
97
+ const sourceColumn = columnNameForAttribute(record, definition.sourceAttribute)
98
+ const foreignKeyColumn = record.getModelClass().getRelationshipByName(definition.belongsTo).getForeignKey()
99
+
100
+ const newSource = record.readAttribute(definition.sourceAttribute)
101
+ const oldSource = oldSourceValueThroughReadCast(record, definition.sourceAttribute, sourceColumn, changes, newSource)
102
+
103
+ const newParentId = currentParentId(record, definition)
104
+ const oldParentId = foreignKeyColumn in changes ? changes[foreignKeyColumn][0] : newParentId
105
+
106
+ return {
107
+ newMagnitude: definition.magnitude(newSource),
108
+ oldMagnitude: definition.magnitude(oldSource),
109
+ newParentId,
110
+ oldParentId
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Applies a captured magnitude change to the parent counter(s) as atomic increments.
116
+ * @param {import("./index.js").default} record - Record that was saved.
117
+ * @param {MagnitudeCounterCacheDefinition} definition - Counter cache definition.
118
+ * @param {PendingMagnitudeDelta} pending - The captured magnitude change.
119
+ * @returns {Promise<void>}
120
+ */
121
+ async function applyPendingMagnitudeDelta(record, definition, pending) {
122
+ const {newMagnitude, oldMagnitude, newParentId, oldParentId} = pending
123
+
124
+ if (oldParentId === newParentId) {
125
+ const delta = newMagnitude - oldMagnitude
126
+
127
+ if (newParentId && delta !== 0) {
128
+ await incrementParentCounter(record, definition, newParentId, delta)
129
+ }
130
+
131
+ return
132
+ }
133
+
134
+ // The foreign key moved: take the old magnitude off the old parent and put the
135
+ // new magnitude on the new parent.
136
+ if (oldParentId && oldMagnitude !== 0) {
137
+ await incrementParentCounter(record, definition, oldParentId, -oldMagnitude)
138
+ }
139
+
140
+ if (newParentId && newMagnitude !== 0) {
141
+ await incrementParentCounter(record, definition, newParentId, newMagnitude)
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Reads the pre-save value of the source attribute through the normal read/cast
147
+ * path, so `magnitude` receives the old value in the same shape as the new one
148
+ * (e.g. a declared boolean as `true`/`false`, not the raw stored `1`/`0`). Drops
149
+ * the pending change so `readAttribute` falls back to the committed value and
150
+ * applies the same cast a fresh read would, then restores the pending change.
151
+ * @param {import("./index.js").default} record - Record being saved.
152
+ * @param {string} sourceAttribute - Source attribute name.
153
+ * @param {string} sourceColumn - Source column name.
154
+ * @param {Record<string, ?>} changes - The record's pre-save changes (column-keyed).
155
+ * @param {?} currentValue - The current (new) read value, returned when the source did not change.
156
+ * @returns {?} The read-cast pre-save value.
157
+ */
158
+ function oldSourceValueThroughReadCast(record, sourceAttribute, sourceColumn, changes, currentValue) {
159
+ if (!(sourceColumn in changes)) {
160
+ return currentValue
161
+ }
162
+
163
+ /**
164
+ * Dynamic record.
165
+ * @type {?} */
166
+ const dynamicRecord = record
167
+ const pendingChange = dynamicRecord._changes[sourceColumn]
168
+
169
+ delete dynamicRecord._changes[sourceColumn]
170
+
171
+ try {
172
+ return record.readAttribute(sourceAttribute)
173
+ } finally {
174
+ dynamicRecord._changes[sourceColumn] = pendingChange
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Reads the record's current foreign-key value for the counter-cache parent.
180
+ * @param {import("./index.js").default} record - Record whose parent is targeted.
181
+ * @param {MagnitudeCounterCacheDefinition} definition - Counter cache definition.
182
+ * @returns {?} The current foreign-key value.
183
+ */
184
+ function currentParentId(record, definition) {
185
+ const foreignKeyColumn = record.getModelClass().getRelationshipByName(definition.belongsTo).getForeignKey()
186
+
187
+ return record.readAttribute(attributeNameForColumn(record, foreignKeyColumn))
188
+ }
189
+
190
+ /**
191
+ * Atomically adds `amount` to the parent's counter column for one parent row.
192
+ * @param {import("./index.js").default} record - Child record (for connection + relationship metadata).
193
+ * @param {MagnitudeCounterCacheDefinition} definition - Counter cache definition.
194
+ * @param {?} parentId - Parent primary-key value.
195
+ * @param {number} amount - Signed integer to add.
196
+ * @returns {Promise<void>}
197
+ */
198
+ async function incrementParentCounter(record, definition, parentId, amount) {
199
+ const modelClass = record.getModelClass()
200
+ const relationship = modelClass.getRelationshipByName(definition.belongsTo)
201
+ const parentModelClass = relationship.getTargetModelClass()
202
+
203
+ if (!parentModelClass) {
204
+ throw new Error(`magnitudeCounterCache on ${modelClass.getModelName()} could not resolve the "${definition.belongsTo}" parent model class`)
205
+ }
206
+
207
+ // Update through the PARENT model's connection: the row being modified belongs
208
+ // to the parent, which may live on a different database/tenant than the child.
209
+ const db = parentModelClass.connection()
210
+ const counterColumnSql = db.quoteColumn(definition.counterColumn)
211
+ const truncatedAmount = Math.trunc(amount)
212
+
213
+ await db.query(
214
+ `UPDATE ${db.quoteTable(parentModelClass.tableName())} ` +
215
+ `SET ${counterColumnSql} = COALESCE(${counterColumnSql}, 0) + ${truncatedAmount} ` +
216
+ `WHERE ${db.quoteColumn(relationship.getPrimaryKey())} = ${db.quote(parentId)}`
217
+ )
218
+ }
219
+
220
+ /**
221
+ * Resolves the column name backing an attribute name.
222
+ * @param {import("./index.js").default} record - Record.
223
+ * @param {string} attributeName - Attribute name.
224
+ * @returns {string} The column name for the attribute (falls back to the attribute name).
225
+ */
226
+ function columnNameForAttribute(record, attributeName) {
227
+ return record.getModelClass().getAttributeNameToColumnNameMap()[attributeName] || attributeName
228
+ }
229
+
230
+ /**
231
+ * Resolves the attribute name backing a column name.
232
+ * @param {import("./index.js").default} record - Record.
233
+ * @param {string} columnName - Column name.
234
+ * @returns {string} The attribute name for the column (falls back to the column name).
235
+ */
236
+ function attributeNameForColumn(record, columnName) {
237
+ const map = record.getModelClass().getAttributeNameToColumnNameMap()
238
+
239
+ return Object.keys(map).find((attributeName) => map[attributeName] === columnName) || columnName
240
+ }
@@ -47,6 +47,8 @@ import {defineModelScope} from "../../utils/model-scope.js"
47
47
  import { normalizeDateStringForWrite, normalizeDateValueForRead, normalizeDateValueForWrite } from "../datetime-storage.js"
48
48
  import {formatValue} from "../../utils/format-value.js"
49
49
  import {captureCreateAuditChanges, captureUpdateAuditChanges, createAudit, createCreateAudit, createDestroyAudit, createUpdateAudit, registerAuditCallback, registerAuditing, withoutAudit} from "./auditing.js"
50
+ import {registerMagnitudeCounterCache} from "./counter-cache-magnitude.js"
51
+ import {stateMachine} from "./state-machine.js"
50
52
  import ValidatorsFormat from "./validators/format.js"
51
53
  import ValidatorsLength from "./validators/length.js"
52
54
  import ValidatorsPresence from "./validators/presence.js"
@@ -668,6 +670,30 @@ class VelociousDatabaseRecord {
668
670
  registerAuditing(this)
669
671
  }
670
672
 
673
+ /**
674
+ * Declares an aasm-style state machine on this model: named states, events
675
+ * (guarded transitions), and enter/exit + before/after transition hooks. See
676
+ * `state-machine.js`. Generates `event()` / `eventAndSave()` / `canEvent()`
677
+ * transition methods per declared event.
678
+ * @param {import("./state-machine.js").StateMachineDefinition} definition - State machine definition.
679
+ * @returns {void}
680
+ */
681
+ static stateMachine(definition) {
682
+ stateMachine(this, definition)
683
+ }
684
+
685
+ /**
686
+ * Maintains a counter column on a `belongsTo` parent as the sum of a per-record
687
+ * magnitude, kept current by atomic increments diffed on every create/update/
688
+ * destroy (and moved between parents when the foreign key changes). See
689
+ * `counter-cache-magnitude.js`.
690
+ * @param {import("./counter-cache-magnitude.js").MagnitudeCounterCacheDefinition} definition - Counter cache definition.
691
+ * @returns {void}
692
+ */
693
+ static magnitudeCounterCache(definition) {
694
+ registerMagnitudeCounterCache(this, definition)
695
+ }
696
+
671
697
  /**
672
698
  * Registers a callback invoked after this model writes an audit row for the action.
673
699
  * @template {typeof VelociousDatabaseRecord} MC
@@ -10,7 +10,9 @@
10
10
  * }} StateMachineDefinition
11
11
  * @typedef {{
12
12
  * beforeEnter?: (model: import("./index.js").default) => void | Promise<void>,
13
- * afterEnter?: (model: import("./index.js").default) => void | Promise<void>
13
+ * afterEnter?: (model: import("./index.js").default) => void | Promise<void>,
14
+ * beforeExit?: (model: import("./index.js").default) => void | Promise<void>,
15
+ * afterExit?: (model: import("./index.js").default) => void | Promise<void>
14
16
  * }} StateDefinition
15
17
  * @typedef {{
16
18
  * from: string | string[],
@@ -68,6 +70,14 @@ export function stateMachine(ModelClass, definition) {
68
70
  * @type {?} */
69
71
  const dynamicClass = ModelClass
70
72
 
73
+ // Idempotent: re-declaring on the same class (or a re-evaluated module) must not
74
+ // register the before/after-save transition hooks twice. Guard on an own property
75
+ // so a subclass declaring its own machine is unaffected by the parent's flag.
76
+ if (Object.prototype.hasOwnProperty.call(dynamicClass, "_stateMachineRegistered") && dynamicClass._stateMachineRegistered) {
77
+ return
78
+ }
79
+
80
+ dynamicClass._stateMachineRegistered = true
71
81
  dynamicClass._stateMachineDefinition = definition
72
82
  dynamicClass._stateMachineColumn = column
73
83
 
@@ -229,7 +239,13 @@ export function stateMachine(ModelClass, definition) {
229
239
  await eventDef.before(model)
230
240
  }
231
241
 
232
- // Run state-level beforeEnter callback
242
+ // Run the exited state's beforeExit, then the entered state's beforeEnter
243
+ const fromStateDefinition = definition.states[pending.from]
244
+
245
+ if (fromStateDefinition?.beforeExit) {
246
+ await fromStateDefinition.beforeExit(model)
247
+ }
248
+
233
249
  const stateDefinition = definition.states[pending.to]
234
250
 
235
251
  if (stateDefinition?.beforeEnter) {
@@ -252,13 +268,19 @@ export function stateMachine(ModelClass, definition) {
252
268
  // Clear the pending transition now that save is complete
253
269
  dynamicModel[PENDING_TRANSITION_KEY] = null
254
270
 
255
- // Run state-level afterEnter callback
271
+ // Run the entered state's afterEnter, then the exited state's afterExit
256
272
  const stateDefinition = definition.states[pending.to]
257
273
 
258
274
  if (stateDefinition?.afterEnter) {
259
275
  await stateDefinition.afterEnter(model)
260
276
  }
261
277
 
278
+ const fromStateDefinition = definition.states[pending.from]
279
+
280
+ if (fromStateDefinition?.afterExit) {
281
+ await fromStateDefinition.afterExit(model)
282
+ }
283
+
262
284
  // Run event-level after callback
263
285
  const eventDef = definition.events[pending.eventName]
264
286
 
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Reactive counter-cache driven by a per-record magnitude.
3
+ *
4
+ * Maintains a counter column on a `belongsTo` parent as the running sum of each
5
+ * child's magnitude — a small number derived from one source attribute (e.g.
6
+ * `1` while a build's `status` is `running`, else `0`). On every create, update
7
+ * and destroy the change in magnitude is applied to the parent as a single
8
+ * atomic increment (`SET col = col + delta`), and when the foreign key changes
9
+ * the magnitude is moved from the old parent to the new one.
10
+ *
11
+ * Because the counter is derived from the source attribute and diffed on every
12
+ * write, it follows that attribute automatically no matter which code path wrote
13
+ * it — there is no per-transition increment/decrement to forget. The old value is
14
+ * captured in `beforeSave` (Velocious clears `changes()` during the post-update
15
+ * reload) and consumed in `afterSave`, so the increment commits atomically with
16
+ * the row it reflects.
17
+ * @typedef {{
18
+ * belongsTo: string,
19
+ * counterColumn: string,
20
+ * sourceAttribute: string,
21
+ * magnitude: (sourceValue: ?) => number
22
+ * }} MagnitudeCounterCacheDefinition
23
+ */
24
+ /**
25
+ * Captured pending magnitude change stashed on a record between beforeSave and afterSave.
26
+ * @typedef {{newMagnitude: number, oldMagnitude: number, newParentId: ?, oldParentId: ?}} PendingMagnitudeDelta
27
+ */
28
+ /**
29
+ * Registers a reactive magnitude counter-cache on a model class.
30
+ * @param {typeof import("./index.js").default} modelClass - Model class to add the counter cache to.
31
+ * @param {MagnitudeCounterCacheDefinition} definition - Counter cache definition.
32
+ * @returns {void}
33
+ */
34
+ export function registerMagnitudeCounterCache(modelClass: typeof import("./index.js").default, definition: MagnitudeCounterCacheDefinition): void;
35
+ /**
36
+ * Reactive counter-cache driven by a per-record magnitude.
37
+ *
38
+ * Maintains a counter column on a `belongsTo` parent as the running sum of each
39
+ * child's magnitude — a small number derived from one source attribute (e.g.
40
+ * `1` while a build's `status` is `running`, else `0`). On every create, update
41
+ * and destroy the change in magnitude is applied to the parent as a single
42
+ * atomic increment (`SET col = col + delta`), and when the foreign key changes
43
+ * the magnitude is moved from the old parent to the new one.
44
+ *
45
+ * Because the counter is derived from the source attribute and diffed on every
46
+ * write, it follows that attribute automatically no matter which code path wrote
47
+ * it — there is no per-transition increment/decrement to forget. The old value is
48
+ * captured in `beforeSave` (Velocious clears `changes()` during the post-update
49
+ * reload) and consumed in `afterSave`, so the increment commits atomically with
50
+ * the row it reflects.
51
+ */
52
+ export type MagnitudeCounterCacheDefinition = {
53
+ belongsTo: string;
54
+ counterColumn: string;
55
+ sourceAttribute: string;
56
+ magnitude: (sourceValue: unknown) => number;
57
+ };
58
+ /**
59
+ * Captured pending magnitude change stashed on a record between beforeSave and afterSave.
60
+ */
61
+ export type PendingMagnitudeDelta = {
62
+ newMagnitude: number;
63
+ oldMagnitude: number;
64
+ newParentId: unknown;
65
+ oldParentId: unknown;
66
+ };
67
+ //# sourceMappingURL=counter-cache-magnitude.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"counter-cache-magnitude.d.ts","sourceRoot":"","sources":["../../../../src/database/record/counter-cache-magnitude.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH;;;GAGG;AAEH;;;;;GAKG;AACH,0DAJW,cAAc,YAAY,EAAE,OAAO,cACnC,+BAA+B,GAC7B,IAAI,CAiDhB;;;;;;;;;;;;;;;;;;8CAlEY;IACR,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,CAAC,WAAW,EAAE,OAAC,KAAK,MAAM,CAAA;CACtC;;;;oCAKS;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,OAAC,CAAC;IAAC,WAAW,EAAE,OAAC,CAAA;CAAC"}
@@ -0,0 +1,204 @@
1
+ // @ts-check
2
+ /**
3
+ * Reactive counter-cache driven by a per-record magnitude.
4
+ *
5
+ * Maintains a counter column on a `belongsTo` parent as the running sum of each
6
+ * child's magnitude — a small number derived from one source attribute (e.g.
7
+ * `1` while a build's `status` is `running`, else `0`). On every create, update
8
+ * and destroy the change in magnitude is applied to the parent as a single
9
+ * atomic increment (`SET col = col + delta`), and when the foreign key changes
10
+ * the magnitude is moved from the old parent to the new one.
11
+ *
12
+ * Because the counter is derived from the source attribute and diffed on every
13
+ * write, it follows that attribute automatically no matter which code path wrote
14
+ * it — there is no per-transition increment/decrement to forget. The old value is
15
+ * captured in `beforeSave` (Velocious clears `changes()` during the post-update
16
+ * reload) and consumed in `afterSave`, so the increment commits atomically with
17
+ * the row it reflects.
18
+ * @typedef {{
19
+ * belongsTo: string,
20
+ * counterColumn: string,
21
+ * sourceAttribute: string,
22
+ * magnitude: (sourceValue: ?) => number
23
+ * }} MagnitudeCounterCacheDefinition
24
+ */
25
+ /**
26
+ * Captured pending magnitude change stashed on a record between beforeSave and afterSave.
27
+ * @typedef {{newMagnitude: number, oldMagnitude: number, newParentId: ?, oldParentId: ?}} PendingMagnitudeDelta
28
+ */
29
+ /**
30
+ * Registers a reactive magnitude counter-cache on a model class.
31
+ * @param {typeof import("./index.js").default} modelClass - Model class to add the counter cache to.
32
+ * @param {MagnitudeCounterCacheDefinition} definition - Counter cache definition.
33
+ * @returns {void}
34
+ */
35
+ export function registerMagnitudeCounterCache(modelClass, definition) {
36
+ /**
37
+ * Dynamic class.
38
+ * @type {?} */
39
+ const dynamicClass = modelClass;
40
+ const registeredFlag = `_magnitudeCounterCacheRegistered_${definition.counterColumn}`;
41
+ // Idempotent per counter column: re-declaring (or subclassing) must not double-register.
42
+ if (Object.prototype.hasOwnProperty.call(dynamicClass, registeredFlag) && dynamicClass[registeredFlag]) {
43
+ return;
44
+ }
45
+ dynamicClass[registeredFlag] = true;
46
+ const pendingKey = `_magnitudeCounterCachePending_${definition.counterColumn}`;
47
+ modelClass.beforeSave(async function (record) {
48
+ /**
49
+ * Dynamic record.
50
+ * @type {?} */
51
+ const dynamicRecord = record;
52
+ dynamicRecord[pendingKey] = computePendingMagnitudeDelta(record, definition);
53
+ });
54
+ modelClass.afterSave(async function (record) {
55
+ /**
56
+ * Dynamic record.
57
+ * @type {?} */
58
+ const dynamicRecord = record;
59
+ const pending = /** @type {PendingMagnitudeDelta | null} */ (dynamicRecord[pendingKey]);
60
+ dynamicRecord[pendingKey] = null;
61
+ if (pending) {
62
+ await applyPendingMagnitudeDelta(record, definition, pending);
63
+ }
64
+ });
65
+ modelClass.afterDestroy(async function (record) {
66
+ const magnitude = definition.magnitude(record.readAttribute(definition.sourceAttribute));
67
+ const parentId = currentParentId(record, definition);
68
+ if (parentId && magnitude !== 0) {
69
+ await incrementParentCounter(record, definition, parentId, -magnitude);
70
+ }
71
+ });
72
+ }
73
+ /**
74
+ * Diffs the source attribute (and foreign key) across the pending save to capture
75
+ * how the parent counter should move. Runs in beforeSave, where `changes()` still
76
+ * holds the pre-save values.
77
+ * @param {import("./index.js").default} record - Record being saved.
78
+ * @param {MagnitudeCounterCacheDefinition} definition - Counter cache definition.
79
+ * @returns {PendingMagnitudeDelta} The captured magnitude change.
80
+ */
81
+ function computePendingMagnitudeDelta(record, definition) {
82
+ const changes = record.changes();
83
+ const sourceColumn = columnNameForAttribute(record, definition.sourceAttribute);
84
+ const foreignKeyColumn = record.getModelClass().getRelationshipByName(definition.belongsTo).getForeignKey();
85
+ const newSource = record.readAttribute(definition.sourceAttribute);
86
+ const oldSource = oldSourceValueThroughReadCast(record, definition.sourceAttribute, sourceColumn, changes, newSource);
87
+ const newParentId = currentParentId(record, definition);
88
+ const oldParentId = foreignKeyColumn in changes ? changes[foreignKeyColumn][0] : newParentId;
89
+ return {
90
+ newMagnitude: definition.magnitude(newSource),
91
+ oldMagnitude: definition.magnitude(oldSource),
92
+ newParentId,
93
+ oldParentId
94
+ };
95
+ }
96
+ /**
97
+ * Applies a captured magnitude change to the parent counter(s) as atomic increments.
98
+ * @param {import("./index.js").default} record - Record that was saved.
99
+ * @param {MagnitudeCounterCacheDefinition} definition - Counter cache definition.
100
+ * @param {PendingMagnitudeDelta} pending - The captured magnitude change.
101
+ * @returns {Promise<void>}
102
+ */
103
+ async function applyPendingMagnitudeDelta(record, definition, pending) {
104
+ const { newMagnitude, oldMagnitude, newParentId, oldParentId } = pending;
105
+ if (oldParentId === newParentId) {
106
+ const delta = newMagnitude - oldMagnitude;
107
+ if (newParentId && delta !== 0) {
108
+ await incrementParentCounter(record, definition, newParentId, delta);
109
+ }
110
+ return;
111
+ }
112
+ // The foreign key moved: take the old magnitude off the old parent and put the
113
+ // new magnitude on the new parent.
114
+ if (oldParentId && oldMagnitude !== 0) {
115
+ await incrementParentCounter(record, definition, oldParentId, -oldMagnitude);
116
+ }
117
+ if (newParentId && newMagnitude !== 0) {
118
+ await incrementParentCounter(record, definition, newParentId, newMagnitude);
119
+ }
120
+ }
121
+ /**
122
+ * Reads the pre-save value of the source attribute through the normal read/cast
123
+ * path, so `magnitude` receives the old value in the same shape as the new one
124
+ * (e.g. a declared boolean as `true`/`false`, not the raw stored `1`/`0`). Drops
125
+ * the pending change so `readAttribute` falls back to the committed value and
126
+ * applies the same cast a fresh read would, then restores the pending change.
127
+ * @param {import("./index.js").default} record - Record being saved.
128
+ * @param {string} sourceAttribute - Source attribute name.
129
+ * @param {string} sourceColumn - Source column name.
130
+ * @param {Record<string, ?>} changes - The record's pre-save changes (column-keyed).
131
+ * @param {?} currentValue - The current (new) read value, returned when the source did not change.
132
+ * @returns {?} The read-cast pre-save value.
133
+ */
134
+ function oldSourceValueThroughReadCast(record, sourceAttribute, sourceColumn, changes, currentValue) {
135
+ if (!(sourceColumn in changes)) {
136
+ return currentValue;
137
+ }
138
+ /**
139
+ * Dynamic record.
140
+ * @type {?} */
141
+ const dynamicRecord = record;
142
+ const pendingChange = dynamicRecord._changes[sourceColumn];
143
+ delete dynamicRecord._changes[sourceColumn];
144
+ try {
145
+ return record.readAttribute(sourceAttribute);
146
+ }
147
+ finally {
148
+ dynamicRecord._changes[sourceColumn] = pendingChange;
149
+ }
150
+ }
151
+ /**
152
+ * Reads the record's current foreign-key value for the counter-cache parent.
153
+ * @param {import("./index.js").default} record - Record whose parent is targeted.
154
+ * @param {MagnitudeCounterCacheDefinition} definition - Counter cache definition.
155
+ * @returns {?} The current foreign-key value.
156
+ */
157
+ function currentParentId(record, definition) {
158
+ const foreignKeyColumn = record.getModelClass().getRelationshipByName(definition.belongsTo).getForeignKey();
159
+ return record.readAttribute(attributeNameForColumn(record, foreignKeyColumn));
160
+ }
161
+ /**
162
+ * Atomically adds `amount` to the parent's counter column for one parent row.
163
+ * @param {import("./index.js").default} record - Child record (for connection + relationship metadata).
164
+ * @param {MagnitudeCounterCacheDefinition} definition - Counter cache definition.
165
+ * @param {?} parentId - Parent primary-key value.
166
+ * @param {number} amount - Signed integer to add.
167
+ * @returns {Promise<void>}
168
+ */
169
+ async function incrementParentCounter(record, definition, parentId, amount) {
170
+ const modelClass = record.getModelClass();
171
+ const relationship = modelClass.getRelationshipByName(definition.belongsTo);
172
+ const parentModelClass = relationship.getTargetModelClass();
173
+ if (!parentModelClass) {
174
+ throw new Error(`magnitudeCounterCache on ${modelClass.getModelName()} could not resolve the "${definition.belongsTo}" parent model class`);
175
+ }
176
+ // Update through the PARENT model's connection: the row being modified belongs
177
+ // to the parent, which may live on a different database/tenant than the child.
178
+ const db = parentModelClass.connection();
179
+ const counterColumnSql = db.quoteColumn(definition.counterColumn);
180
+ const truncatedAmount = Math.trunc(amount);
181
+ await db.query(`UPDATE ${db.quoteTable(parentModelClass.tableName())} ` +
182
+ `SET ${counterColumnSql} = COALESCE(${counterColumnSql}, 0) + ${truncatedAmount} ` +
183
+ `WHERE ${db.quoteColumn(relationship.getPrimaryKey())} = ${db.quote(parentId)}`);
184
+ }
185
+ /**
186
+ * Resolves the column name backing an attribute name.
187
+ * @param {import("./index.js").default} record - Record.
188
+ * @param {string} attributeName - Attribute name.
189
+ * @returns {string} The column name for the attribute (falls back to the attribute name).
190
+ */
191
+ function columnNameForAttribute(record, attributeName) {
192
+ return record.getModelClass().getAttributeNameToColumnNameMap()[attributeName] || attributeName;
193
+ }
194
+ /**
195
+ * Resolves the attribute name backing a column name.
196
+ * @param {import("./index.js").default} record - Record.
197
+ * @param {string} columnName - Column name.
198
+ * @returns {string} The attribute name for the column (falls back to the column name).
199
+ */
200
+ function attributeNameForColumn(record, columnName) {
201
+ const map = record.getModelClass().getAttributeNameToColumnNameMap();
202
+ return Object.keys(map).find((attributeName) => map[attributeName] === columnName) || columnName;
203
+ }
204
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY291bnRlci1jYWNoZS1tYWduaXR1ZGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvZGF0YWJhc2UvcmVjb3JkL2NvdW50ZXItY2FjaGUtbWFnbml0dWRlLmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLFlBQVk7QUFFWjs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQXNCRztBQUVIOzs7R0FHRztBQUVIOzs7OztHQUtHO0FBQ0gsTUFBTSxVQUFVLDZCQUE2QixDQUFDLFVBQVUsRUFBRSxVQUFVO0lBQ2xFOzttQkFFZTtJQUNmLE1BQU0sWUFBWSxHQUFHLFVBQVUsQ0FBQTtJQUMvQixNQUFNLGNBQWMsR0FBRyxvQ0FBb0MsVUFBVSxDQUFDLGFBQWEsRUFBRSxDQUFBO0lBRXJGLHlGQUF5RjtJQUN6RixJQUFJLE1BQU0sQ0FBQyxTQUFTLENBQUMsY0FBYyxDQUFDLElBQUksQ0FBQyxZQUFZLEVBQUUsY0FBYyxDQUFDLElBQUksWUFBWSxDQUFDLGNBQWMsQ0FBQyxFQUFFLENBQUM7UUFDdkcsT0FBTTtJQUNSLENBQUM7SUFFRCxZQUFZLENBQUMsY0FBYyxDQUFDLEdBQUcsSUFBSSxDQUFBO0lBRW5DLE1BQU0sVUFBVSxHQUFHLGlDQUFpQyxVQUFVLENBQUMsYUFBYSxFQUFFLENBQUE7SUFFOUUsVUFBVSxDQUFDLFVBQVUsQ0FBQyxLQUFLLFdBQVcsTUFBTTtRQUMxQzs7dUJBRWU7UUFDZixNQUFNLGFBQWEsR0FBRyxNQUFNLENBQUE7UUFFNUIsYUFBYSxDQUFDLFVBQVUsQ0FBQyxHQUFHLDRCQUE0QixDQUFDLE1BQU0sRUFBRSxVQUFVLENBQUMsQ0FBQTtJQUM5RSxDQUFDLENBQUMsQ0FBQTtJQUVGLFVBQVUsQ0FBQyxTQUFTLENBQUMsS0FBSyxXQUFXLE1BQU07UUFDekM7O3VCQUVlO1FBQ2YsTUFBTSxhQUFhLEdBQUcsTUFBTSxDQUFBO1FBQzVCLE1BQU0sT0FBTyxHQUFHLDJDQUEyQyxDQUFDLENBQUMsYUFBYSxD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RztBQUNILFNBQVMsc0JBQXNCLENBQUMsTUFBTSxFQUFFLFVBQVU7SUFDaEQsTUFBTSxHQUFHLEdBQUcsTUFBTSxDQUFDLGFBQWEsRUFBRSxDQUFDLCtCQUErQixFQUFFLENBQUE7SUFFcEUsT0FBTyxNQUFNLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLGFBQWEsRUFBRSxFQUFFLENBQUMsR0FBRyxDQUFDLGFBQWEsQ0FBQyxLQUFLLFVBQVUsQ0FBQyxJQUFJLFVBQVUsQ0FBQTtBQUNsRyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQHRzLWNoZWNrXG5cbi8qKlxuICogUmVhY3RpdmUgY291bnRlci1jYWNoZSBkcml2ZW4gYnkgYSBwZXItcmVjb3JkIG1hZ25pdHVkZS5cbiAqXG4gKiBNYWludGFpbnMgYSBjb3VudGVyIGNvbHVtbiBvbiBhIGBiZWxvbmdzVG9gIHBhcmVudCBhcyB0aGUgcnVubmluZyBzdW0gb2YgZWFjaFxuICogY2hpbGQncyBtYWduaXR1ZGUg4oCUIGEgc21hbGwgbnVtYmVyIGRlcml2ZWQgZnJvbSBvbmUgc291cmNlIGF0dHJpYnV0ZSAoZS5nLlxuICogYDFgIHdoaWxlIGEgYnVpbGQncyBgc3RhdHVzYCBpcyBgcnVubmluZ2AsIGVsc2UgYDBgKS4gT24gZXZlcnkgY3JlYXRlLCB1cGRhdGVcbiAqIGFuZCBkZXN0cm95IHRoZSBjaGFuZ2UgaW4gbWFnbml0dWRlIGlzIGFwcGxpZWQgdG8gdGhlIHBhcmVudCBhcyBhIHNpbmdsZVxuICogYXRvbWljIGluY3JlbWVudCAoYFNFVCBjb2wgPSBjb2wgKyBkZWx0YWApLCBhbmQgd2hlbiB0aGUgZm9yZWlnbiBrZXkgY2hhbmdlc1xuICogdGhlIG1hZ25pdHVkZSBpcyBtb3ZlZCBmcm9tIHRoZSBvbGQgcGFyZW50IHRvIHRoZSBuZXcgb25lLlxuICpcbiAqIEJlY2F1c2UgdGhlIGNvdW50ZXIgaXMgZGVyaXZlZCBmcm9tIHRoZSBzb3VyY2UgYXR0cmlidXRlIGFuZCBkaWZmZWQgb24gZXZlcnlcbiAqIHdyaXRlLCBpdCBmb2xsb3dzIHRoYXQgYXR0cmlidXRlIGF1dG9tYXRpY2FsbHkgbm8gbWF0dGVyIHdoaWNoIGNvZGUgcGF0aCB3cm90ZVxuICogaXQg4oCUIHRoZXJlIGlzIG5vIHBlci10cmFuc2l0aW9uIGluY3JlbWVudC9kZWNyZW1lbnQgdG8gZm9yZ2V0LiBUaGUgb2xkIHZhbHVlIGlzXG4gKiBjYXB0dXJlZCBpbiBgYmVmb3JlU2F2ZWAgKFZlbG9jaW91cyBjbGVhcnMgYGNoYW5nZXMoKWAgZHVyaW5nIHRoZSBwb3N0LXVwZGF0ZVxuICogcmVsb2FkKSBhbmQgY29uc3VtZWQgaW4gYGFmdGVyU2F2ZWAsIHNvIHRoZSBpbmNyZW1lbnQgY29tbWl0cyBhdG9taWNhbGx5IHdpdGhcbiAqIHRoZSByb3cgaXQgcmVmbGVjdHMuXG4gKiBAdHlwZWRlZiB7e1xuICogICBiZWxvbmdzVG86IHN0cmluZyxcbiAqICAgY291bnRlckNvbHVtbjogc3RyaW5nLFxuICogICBzb3VyY2VBdHRyaWJ1dGU6IHN0cmluZyxcbiAqICAgbWFnbml0dWRlOiAoc291cmNlVmFsdWU6ID8pID0+IG51bWJlclxuICogfX0gTWFnbml0dWRlQ291bnRlckNhY2hlRGVmaW5pdGlvblxuICovXG5cbi8qKlxuICogQ2FwdHVyZWQgcGVuZGluZyBtYWduaXR1ZGUgY2hhbmdlIHN0YXNoZWQgb24gYSByZWNvcmQgYmV0d2VlbiBiZWZvcmVTYXZlIGFuZCBhZnRlclNhdmUuXG4gKiBAdHlwZWRlZiB7e25ld01hZ25pdHVkZTogbnVtYmVyLCBvbGRNYWduaXR1ZGU6IG51bWJlciwgbmV3UGFyZW50SWQ6ID8sIG9sZFBhcmVudElkOiA/fX0gUGVuZGluZ01hZ25pdHVkZURlbHRhXG4gKi9cblxuLyoqXG4gKiBSZWdpc3RlcnMgYSByZWFjdGl2ZSBtYWduaXR1ZGUgY291bnRlci1jYWNoZSBvbiBhIG1vZGVsIGNsYXNzLlxuICogQHBhcmFtIHt0eXBlb2YgaW1wb3J0KFwiLi9pbmRleC5qc1wiKS5kZWZhdWx0fSBtb2RlbENsYXNzIC0gTW9kZWwgY2xhc3MgdG8gYWRkIHRoZSBjb3VudGVyIGNhY2hlIHRvLlxuICogQHBhcmFtIHtNYWduaXR1ZGVDb3VudGVyQ2FjaGVEZWZpbml0aW9ufSBkZWZpbml0aW9uIC0gQ291bnRlciBjYWNoZSBkZWZpbml0aW9uLlxuICogQHJldHVybnMge3ZvaWR9XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiByZWdpc3Rlck1hZ25pdHVkZUNvdW50ZXJDYWNoZShtb2RlbENsYXNzLCBkZWZpbml0aW9uKSB7XG4gIC8qKlxuICAgKiBEeW5hbWljIGNsYXNzLlxuICAgKiBAdHlwZSB7P30gKi9cbiAgY29uc3QgZHluYW1pY0NsYXNzID0gbW9kZWxDbGFzc1xuICBjb25zdCByZWdpc3RlcmVkRmxhZyA9IGBfbWFnbml0dWRlQ291bnRlckNhY2hlUmVnaXN0ZXJlZF8ke2RlZmluaXRpb24uY291bnRlckNvbHVtbn1gXG5cbiAgLy8gSWRlbXBvdGVudCBwZXIgY291bnRlciBjb2x1bW46IHJlLWRlY2xhcmluZyAob3Igc3ViY2xhc3NpbmcpIG11c3Qgbm90IGRvdWJsZS1yZWdpc3Rlci5cbiAgaWYgKE9iamVjdC5wcm90b3R5cGUuaGFzT3duUHJvcGVydHkuY2FsbChkeW5hbWljQ2xhc3MsIHJlZ2lzdGVyZWRGbGFnKSAmJiBkeW5hbWljQ2xhc3NbcmVnaXN0ZXJlZEZsYWddKSB7XG4gICAgcmV0dXJuXG4gIH1cblxuICBkeW5hbWljQ2xhc3NbcmVnaXN0ZXJlZEZsYWddID0gdHJ1ZVxuXG4gIGNvbnN0IHBlbmRpbmdLZXkgPSBgX21hZ25pdHVkZUNvdW50ZXJDYWNoZVBlbmRpbmdfJHtkZWZpbml0aW9uLmNvdW50ZXJDb2x1bW59YFxuXG4gIG1vZGVsQ2xhc3MuYmVmb3JlU2F2ZShhc3luYyBmdW5jdGlvbiAocmVjb3JkKSB7XG4gICAgLyoqXG4gICAgICogRHluYW1pYyByZWNvcmQuXG4gICAgICogQHR5cGUgez99ICovXG4gICAgY29uc3QgZHluYW1pY1JlY29yZCA9IHJlY29yZFxuXG4gICAgZHluYW1pY1JlY29yZFtwZW5kaW5nS2V5XSA9IGNvbXB1dGVQZW5kaW5nTWFnbml0dWRlRGVsdGEocmVjb3JkLCBkZWZpbml0aW9uKVxuICB9KVxuXG4gIG1vZGVsQ2xhc3MuYWZ0ZXJTYXZlKGFzeW5jIGZ1bmN0aW9uIChyZWNvcmQpIHtcbiAgICAvKipcbiAgICAgKiBEeW5hbWljIHJlY29yZC5cbiAgICAgKiBAdHlwZSB7P30gKi9cbiAgICBjb25zdCBkeW5hbWljUmVjb3JkID0gcmVjb3JkXG4gICAgY29uc3QgcGVuZGluZyA9IC8qKiBAdHlwZSB7UGVuZGluZ01hZ25pdHVkZURlbHRhIHwgbnVsbH0gKi8gKGR5bmFtaWNSZWNvcmRbcGVuZGluZ0tleV0pXG5cbiAgICBkeW5hbWljUmVjb3JkW3BlbmRpbmdLZXldID0gbnVsbFxuXG4gICAgaWYgKHBlbmRpbmcpIHtcbiAgICAgIGF3YWl0IGFwcGx5UGVuZGluZ01hZ25pdHVkZURlbHRhKHJlY29yZCwgZGVmaW5pdGlvbiwgcGVuZGluZylcbiAgICB9XG4gIH0pXG5cbiAgbW9kZWxDbGFzcy5hZnRlckRlc3Ryb3koYXN5bmMgZnVuY3Rpb24gKHJlY29yZCkge1xuICAgIGNvbnN0IG1hZ25pdHVkZSA9IGRlZmluaXRpb24ubWFnbml0dWRlKHJlY29yZC5yZWFkQXR0cmlidXRlKGRlZmluaXRpb24uc291cmNlQXR0cmlidXRlKSlcbiAgICBjb25zdCBwYXJlbnRJZCA9IGN1cnJlbnRQYXJlbnRJZChyZWNvcmQsIGRlZmluaXRpb24pXG5cbiAgICBpZiAocGFyZW50SWQgJiYgbWFnbml0dWRlICE9PSAwKSB7XG4gICAgICBhd2FpdCBpbmNyZW1lbnRQYXJlbnRDb3VudGVyKHJlY29yZCwgZGVmaW5pdGlvbiwgcGFyZW50SWQsIC1tYWduaXR1ZGUpXG4gICAgfVxuICB9KVxufVxuXG4vKipcbiAqIERpZmZzIHRoZSBzb3VyY2UgYXR0cmlidXRlIChhbmQgZm9yZWlnbiBrZXkpIGFjcm9zcyB0aGUgcGVuZGluZyBzYXZlIHRvIGNhcHR1cmVcbiAqIGhvdyB0aGUgcGFyZW50IGNvdW50ZXIgc2hvdWxkIG1vdmUuIFJ1bnMgaW4gYmVmb3JlU2F2ZSwgd2hlcmUgYGNoYW5nZXMoKWAgc3RpbGxcbiAqIGhvbGRzIHRoZSBwcmUtc2F2ZSB2YWx1ZXMuXG4gKiBAcGFyYW0ge2ltcG9ydChcIi4vaW5kZXguanNcIikuZGVmYXVsdH0gcmVjb3JkIC0gUmVjb3JkIGJlaW5nIHNhdmVkLlxuICogQHBhcmFtIHtNYWduaXR1ZGVDb3VudGVyQ2FjaGVEZWZpbml0aW9ufSBkZWZpbml0aW9uIC0gQ291bnRlciBjYWNoZSBkZWZpbml0aW9uLlxuICogQHJldHVybnMge1BlbmRpbmdNYWduaXR1ZGVEZWx0YX0gVGhlIGNhcHR1cmVkIG1hZ25pdHVkZSBjaGFuZ2UuXG4gKi9cbmZ1bmN0aW9uIGNvbXB1dGVQZW5kaW5nTWFnbml0dWRlRGVsdGEocmVjb3JkLCBkZWZpbml0aW9uKSB7XG4gIGNvbnN0IGNoYW5nZXMgPSByZWNvcmQuY2hhbmdlcygpXG4gIGNvbnN0IHNvdXJjZUNvbHVtbiA9IGNvbHVtbk5hbWVGb3JBdHRyaWJ1dGUocmVjb3JkLCBkZWZpbml0aW9uLnNvdXJjZUF0dHJpYnV0ZSlcbiAgY29uc3QgZm9yZWlnbktleUNvbHVtbiA9IHJlY29yZC5nZXRNb2RlbENsYXNzKCkuZ2V0UmVsYXRpb25zaGlwQnlOYW1lKGRlZmluaXRpb24uYmVsb25nc1RvKS5nZXRGb3JlaWduS2V5KClcblxuICBjb25zdCBuZXdTb3VyY2UgPSByZWNvcmQucmVhZEF0dHJpYnV0ZShkZWZpbml0aW9uLnNvdXJjZUF0dHJpYnV0ZSlcbiAgY29uc3Qgb2xkU291cmNlID0gb2xkU291cmNlVmFsdWVUaHJvdWdoUmVhZENhc3QocmVjb3JkLCBkZWZpbml0aW9uLnNvdXJjZUF0dHJpYnV0ZSwgc291cmNlQ29sdW1uLCBjaGFuZ2VzLCBuZXdTb3VyY2UpXG5cbiAgY29uc3QgbmV3UGFyZW50SWQgPSBjdXJyZW50UGFyZW50SWQocmVjb3JkLCBkZWZpbml0aW9uKVxuICBjb25zdCBvbGRQYXJlbnRJZCA9IGZvcmVpZ25LZXlDb2x1bW4gaW4gY2hhbmdlcyA/IGNoYW5nZXNbZm9yZWlnbktleUNvbHVtbl1bMF0gOiBuZXdQYXJlbnRJZFxuXG4gIHJldHVybiB7XG4gICAgbmV3TWFnbml0dWRlOiBkZWZpbml0aW9uLm1hZ25pdHVkZShuZXdTb3VyY2UpLFxuICAgIG9sZE1hZ25pdHVkZTogZGVmaW5pdGlvbi5tYWduaXR1ZGUob2xkU291cmNlKSxcbiAgICBuZXdQYXJlbnRJZCxcbiAgICBvbGRQYXJlbnRJZFxuICB9XG59XG5cbi8qKlxuICogQXBwbGllcyBhIGNhcHR1cmVkIG1hZ25pdHVkZSBjaGFuZ2UgdG8gdGhlIHBhcmVudCBjb3VudGVyKHMpIGFzIGF0b21pYyBpbmNyZW1lbnRzLlxuICogQHBhcmFtIHtpbXBvcnQoXCIuL2luZGV4LmpzXCIpLmRlZmF1bHR9IHJlY29yZCAtIFJlY29yZCB0aGF0IHdhcyBzYXZlZC5cbiAqIEBwYXJhbSB7TWFnbml0dWRlQ291bnRlckNhY2hlRGVmaW5pdGlvbn0gZGVmaW5pdGlvbiAtIENvdW50ZXIgY2FjaGUgZGVmaW5pdGlvbi5cbiAqIEBwYXJhbSB7UGVuZGluZ01hZ25pdHVkZURlbHRhfSBwZW5kaW5nIC0gVGhlIGNhcHR1cmVkIG1hZ25pdHVkZSBjaGFuZ2UuXG4gKiBAcmV0dXJucyB7UHJvbWlzZTx2b2lkPn1cbiAqL1xuYXN5bmMgZnVuY3Rpb24gYXBwbHlQZW5kaW5nTWFnbml0dWRlRGVsdGEocmVjb3JkLCBkZWZpbml0aW9uLCBwZW5kaW5nKSB7XG4gIGNvbnN0IHtuZXdNYWduaXR1ZGUsIG9sZE1hZ25pdHVkZSwgbmV3UGFyZW50SWQsIG9sZFBhcmVudElkfSA9IHBlbmRpbmdcblxuICBpZiAob2xkUGFyZW50SWQgPT09IG5ld1BhcmVudElkKSB7XG4gICAgY29uc3QgZGVsdGEgPSBuZXdNYWduaXR1ZGUgLSBvbGRNYWduaXR1ZGVcblxuICAgIGlmIChuZXdQYXJlbnRJZCAmJiBkZWx0YSAhPT0gMCkge1xuICAgICAgYXdhaXQgaW5jcmVtZW50UGFyZW50Q291bnRlcihyZWNvcmQsIGRlZmluaXRpb24sIG5ld1BhcmVudElkLCBkZWx0YSlcbiAgICB9XG5cbiAgICByZXR1cm5cbiAgfVxuXG4gIC8vIFRoZSBmb3JlaWduIGtleSBtb3ZlZDogdGFrZSB0aGUgb2xkIG1hZ25pdHVkZSBvZmYgdGhlIG9sZCBwYXJlbnQgYW5kIHB1dCB0aGVcbiAgLy8gbmV3IG1hZ25pdHVkZSBvbiB0aGUgbmV3IHBhcmVudC5cbiAgaWYgKG9sZFBhcmVudElkICYmIG9sZE1hZ25pdHVkZSAhPT0gMCkge1xuICAgIGF3YWl0IGluY3JlbWVudFBhcmVudENvdW50ZXIocmVjb3JkLCBkZWZpbml0aW9uLCBvbGRQYXJlbnRJZCwgLW9sZE1hZ25pdHVkZSlcbiAgfVxuXG4gIGlmIChuZXdQYXJlbnRJZCAmJiBuZXdNYWduaXR1ZGUgIT09IDApIHtcbiAgICBhd2FpdCBpbmNyZW1lbnRQYXJlbnRDb3VudGVyKHJlY29yZCwgZGVmaW5pdGlvbiwgbmV3UGFyZW50SWQsIG5ld01hZ25pdHVkZSlcbiAgfVxufVxuXG4vKipcbiAqIFJlYWRzIHRoZSBwcmUtc2F2ZSB2YWx1ZSBvZiB0aGUgc291cmNlIGF0dHJpYnV0ZSB0aHJvdWdoIHRoZSBub3JtYWwgcmVhZC9jYXN0XG4gKiBwYXRoLCBzbyBgbWFnbml0dWRlYCByZWNlaXZlcyB0aGUgb2xkIHZhbHVlIGluIHRoZSBzYW1lIHNoYXBlIGFzIHRoZSBuZXcgb25lXG4gKiAoZS5nLiBhIGRlY2xhcmVkIGJvb2xlYW4gYXMgYHRydWVgL2BmYWxzZWAsIG5vdCB0aGUgcmF3IHN0b3JlZCBgMWAvYDBgKS4gRHJvcHNcbiAqIHRoZSBwZW5kaW5nIGNoYW5nZSBzbyBgcmVhZEF0dHJpYnV0ZWAgZmFsbHMgYmFjayB0byB0aGUgY29tbWl0dGVkIHZhbHVlIGFuZFxuICogYXBwbGllcyB0aGUgc2FtZSBjYXN0IGEgZnJlc2ggcmVhZCB3b3VsZCwgdGhlbiByZXN0b3JlcyB0aGUgcGVuZGluZyBjaGFuZ2UuXG4gKiBAcGFyYW0ge2ltcG9ydChcIi4vaW5kZXguanNcIikuZGVmYXVsdH0gcmVjb3JkIC0gUmVjb3JkIGJlaW5nIHNhdmVkLlxuICogQHBhcmFtIHtzdHJpbmd9IHNvdXJjZUF0dHJpYnV0ZSAtIFNvdXJjZSBhdHRyaWJ1dGUgbmFtZS5cbiAqIEBwYXJhbSB7c3RyaW5nfSBzb3VyY2VDb2x1bW4gLSBTb3VyY2UgY29sdW1uIG5hbWUuXG4gKiBAcGFyYW0ge1JlY29yZDxzdHJpbmcsID8+fSBjaGFuZ2VzIC0gVGhlIHJlY29yZCdzIHByZS1zYXZlIGNoYW5nZXMgKGNvbHVtbi1rZXllZCkuXG4gKiBAcGFyYW0gez99IGN1cnJlbnRWYWx1ZSAtIFRoZSBjdXJyZW50IChuZXcpIHJlYWQgdmFsdWUsIHJldHVybmVkIHdoZW4gdGhlIHNvdXJjZSBkaWQgbm90IGNoYW5nZS5cbiAqIEByZXR1cm5zIHs/fSBUaGUgcmVhZC1jYXN0IHByZS1zYXZlIHZhbHVlLlxuICovXG5mdW5jdGlvbiBvbGRTb3VyY2VWYWx1ZVRocm91Z2hSZWFkQ2FzdChyZWNvcmQsIHNvdXJjZUF0dHJpYnV0ZSwgc291cmNlQ29sdW1uLCBjaGFuZ2VzLCBjdXJyZW50VmFsdWUpIHtcbiAgaWYgKCEoc291cmNlQ29sdW1uIGluIGNoYW5nZXMpKSB7XG4gICAgcmV0dXJuIGN1cnJlbnRWYWx1ZVxuICB9XG5cbiAgLyoqXG4gICAqIER5bmFtaWMgcmVjb3JkLlxuICAgKiBAdHlwZSB7P30gKi9cbiAgY29uc3QgZHluYW1pY1JlY29yZCA9IHJlY29yZFxuICBjb25zdCBwZW5kaW5nQ2hhbmdlID0gZHluYW1pY1JlY29yZC5fY2hhbmdlc1tzb3VyY2VDb2x1bW5dXG5cbiAgZGVsZXRlIGR5bmFtaWNSZWNvcmQuX2NoYW5nZXNbc291cmNlQ29sdW1uXVxuXG4gIHRyeSB7XG4gICAgcmV0dXJuIHJlY29yZC5yZWFkQXR0cmlidXRlKHNvdXJjZUF0dHJpYnV0ZSlcbiAgfSBmaW5hbGx5IHtcbiAgICBkeW5hbWljUmVjb3JkLl9jaGFuZ2VzW3NvdXJjZUNvbHVtbl0gPSBwZW5kaW5nQ2hhbmdlXG4gIH1cbn1cblxuLyoqXG4gKiBSZWFkcyB0aGUgcmVjb3JkJ3MgY3VycmVudCBmb3JlaWduLWtleSB2YWx1ZSBmb3IgdGhlIGNvdW50ZXItY2FjaGUgcGFyZW50LlxuICogQHBhcmFtIHtpbXBvcnQoXCIuL2luZGV4LmpzXCIpLmRlZmF1bHR9IHJlY29yZCAtIFJlY29yZCB3aG9zZSBwYXJlbnQgaXMgdGFyZ2V0ZWQuXG4gKiBAcGFyYW0ge01hZ25pdHVkZUNvdW50ZXJDYWNoZURlZmluaXRpb259IGRlZmluaXRpb24gLSBDb3VudGVyIGNhY2hlIGRlZmluaXRpb24uXG4gKiBAcmV0dXJucyB7P30gVGhlIGN1cnJlbnQgZm9yZWlnbi1rZXkgdmFsdWUuXG4gKi9cbmZ1bmN0aW9uIGN1cnJlbnRQYXJlbnRJZChyZWNvcmQsIGRlZmluaXRpb24pIHtcbiAgY29uc3QgZm9yZWlnbktleUNvbHVtbiA9IHJlY29yZC5nZXRNb2RlbENsYXNzKCkuZ2V0UmVsYXRpb25zaGlwQnlOYW1lKGRlZmluaXRpb24uYmVsb25nc1RvKS5nZXRGb3JlaWduS2V5KClcblxuICByZXR1cm4gcmVjb3JkLnJlYWRBdHRyaWJ1dGUoYXR0cmlidXRlTmFtZUZvckNvbHVtbihyZWNvcmQsIGZvcmVpZ25LZXlDb2x1bW4pKVxufVxuXG4vKipcbiAqIEF0b21pY2FsbHkgYWRkcyBgYW1vdW50YCB0byB0aGUgcGFyZW50J3MgY291bnRlciBjb2x1bW4gZm9yIG9uZSBwYXJlbnQgcm93LlxuICogQHBhcmFtIHtpbXBvcnQoXCIuL2luZGV4LmpzXCIpLmRlZmF1bHR9IHJlY29yZCAtIENoaWxkIHJlY29yZCAoZm9yIGNvbm5lY3Rpb24gKyByZWxhdGlvbnNoaXAgbWV0YWRhdGEpLlxuICogQHBhcmFtIHtNYWduaXR1ZGVDb3VudGVyQ2FjaGVEZWZpbml0aW9ufSBkZWZpbml0aW9uIC0gQ291bnRlciBjYWNoZSBkZWZpbml0aW9uLlxuICogQHBhcmFtIHs/fSBwYXJlbnRJZCAtIFBhcmVudCBwcmltYXJ5LWtleSB2YWx1ZS5cbiAqIEBwYXJhbSB7bnVtYmVyfSBhbW91bnQgLSBTaWduZWQgaW50ZWdlciB0byBhZGQuXG4gKiBAcmV0dXJucyB7UHJvbWlzZTx2b2lkPn1cbiAqL1xuYXN5bmMgZnVuY3Rpb24gaW5jcmVtZW50UGFyZW50Q291bnRlcihyZWNvcmQsIGRlZmluaXRpb24sIHBhcmVudElkLCBhbW91bnQpIHtcbiAgY29uc3QgbW9kZWxDbGFzcyA9IHJlY29yZC5nZXRNb2RlbENsYXNzKClcbiAgY29uc3QgcmVsYXRpb25zaGlwID0gbW9kZWxDbGFzcy5nZXRSZWxhdGlvbnNoaXBCeU5hbWUoZGVmaW5pdGlvbi5iZWxvbmdzVG8pXG4gIGNvbnN0IHBhcmVudE1vZGVsQ2xhc3MgPSByZWxhdGlvbnNoaXAuZ2V0VGFyZ2V0TW9kZWxDbGFzcygpXG5cbiAgaWYgKCFwYXJlbnRNb2RlbENsYXNzKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKGBtYWduaXR1ZGVDb3VudGVyQ2FjaGUgb24gJHttb2RlbENsYXNzLmdldE1vZGVsTmFtZSgpfSBjb3VsZCBub3QgcmVzb2x2ZSB0aGUgXCIke2RlZmluaXRpb24uYmVsb25nc1RvfVwiIHBhcmVudCBtb2RlbCBjbGFzc2ApXG4gIH1cblxuICAvLyBVcGRhdGUgdGhyb3VnaCB0aGUgUEFSRU5UIG1vZGVsJ3MgY29ubmVjdGlvbjogdGhlIHJvdyBiZWluZyBtb2RpZmllZCBiZWxvbmdzXG4gIC8vIHRvIHRoZSBwYXJlbnQsIHdoaWNoIG1heSBsaXZlIG9uIGEgZGlmZmVyZW50IGRhdGFiYXNlL3RlbmFudCB0aGFuIHRoZSBjaGlsZC5cbiAgY29uc3QgZGIgPSBwYXJlbnRNb2RlbENsYXNzLmNvbm5lY3Rpb24oKVxuICBjb25zdCBjb3VudGVyQ29sdW1uU3FsID0gZGIucXVvdGVDb2x1bW4oZGVmaW5pdGlvbi5jb3VudGVyQ29sdW1uKVxuICBjb25zdCB0cnVuY2F0ZWRBbW91bnQgPSBNYXRoLnRydW5jKGFtb3VudClcblxuICBhd2FpdCBkYi5xdWVyeShcbiAgICBgVVBEQVRFICR7ZGIucXVvdGVUYWJsZShwYXJlbnRNb2RlbENsYXNzLnRhYmxlTmFtZSgpKX0gYCArXG4gICAgYFNFVCAke2NvdW50ZXJDb2x1bW5TcWx9ID0gQ09BTEVTQ0UoJHtjb3VudGVyQ29sdW1uU3FsfSwgMCkgKyAke3RydW5jYXRlZEFtb3VudH0gYCArXG4gICAgYFdIRVJFICR7ZGIucXVvdGVDb2x1bW4ocmVsYXRpb25zaGlwLmdldFByaW1hcnlLZXkoKSl9ID0gJHtkYi5xdW90ZShwYXJlbnRJZCl9YFxuICApXG59XG5cbi8qKlxuICogUmVzb2x2ZXMgdGhlIGNvbHVtbiBuYW1lIGJhY2tpbmcgYW4gYXR0cmlidXRlIG5hbWUuXG4gKiBAcGFyYW0ge2ltcG9ydChcIi4vaW5kZXguanNcIikuZGVmYXVsdH0gcmVjb3JkIC0gUmVjb3JkLlxuICogQHBhcmFtIHtzdHJpbmd9IGF0dHJpYnV0ZU5hbWUgLSBBdHRyaWJ1dGUgbmFtZS5cbiAqIEByZXR1cm5zIHtzdHJpbmd9IFRoZSBjb2x1bW4gbmFtZSBmb3IgdGhlIGF0dHJpYnV0ZSAoZmFsbHMgYmFjayB0byB0aGUgYXR0cmlidXRlIG5hbWUpLlxuICovXG5mdW5jdGlvbiBjb2x1bW5OYW1lRm9yQXR0cmlidXRlKHJlY29yZCwgYXR0cmlidXRlTmFtZSkge1xuICByZXR1cm4gcmVjb3JkLmdldE1vZGVsQ2xhc3MoKS5nZXRBdHRyaWJ1dGVOYW1lVG9Db2x1bW5OYW1lTWFwKClbYXR0cmlidXRlTmFtZV0gfHwgYXR0cmlidXRlTmFtZVxufVxuXG4vKipcbiAqIFJlc29sdmVzIHRoZSBhdHRyaWJ1dGUgbmFtZSBiYWNraW5nIGEgY29sdW1uIG5hbWUuXG4gKiBAcGFyYW0ge2ltcG9ydChcIi4vaW5kZXguanNcIikuZGVmYXVsdH0gcmVjb3JkIC0gUmVjb3JkLlxuICogQHBhcmFtIHtzdHJpbmd9IGNvbHVtbk5hbWUgLSBDb2x1bW4gbmFtZS5cbiAqIEByZXR1cm5zIHtzdHJpbmd9IFRoZSBhdHRyaWJ1dGUgbmFtZSBmb3IgdGhlIGNvbHVtbiAoZmFsbHMgYmFjayB0byB0aGUgY29sdW1uIG5hbWUpLlxuICovXG5mdW5jdGlvbiBhdHRyaWJ1dGVOYW1lRm9yQ29sdW1uKHJlY29yZCwgY29sdW1uTmFtZSkge1xuICBjb25zdCBtYXAgPSByZWNvcmQuZ2V0TW9kZWxDbGFzcygpLmdldEF0dHJpYnV0ZU5hbWVUb0NvbHVtbk5hbWVNYXAoKVxuXG4gIHJldHVybiBPYmplY3Qua2V5cyhtYXApLmZpbmQoKGF0dHJpYnV0ZU5hbWUpID0+IG1hcFthdHRyaWJ1dGVOYW1lXSA9PT0gY29sdW1uTmFtZSkgfHwgY29sdW1uTmFtZVxufVxuIl19