velocious 1.0.650 → 1.0.651

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/README.md CHANGED
@@ -13,7 +13,7 @@
13
13
  * External packages (engines) that contribute data models, frontend-model resources and migrations to a consuming app (see [docs/packages.md](docs/packages.md))
14
14
  * Optional Rampway-owned durable deployment control plane mounted through the standard routes DSL on Velocious 1.0.577 or newer (see [docs/rampway-integration.md](docs/rampway-integration.md))
15
15
  * Controllers and views for HTTP endpoints
16
- * Frontend-model transport for creating, updating, querying, and subscribing to query-filtered lifecycle events over HTTP/WebSocket, with structured per-attribute validation error responses, immutable per-operation remote request context, registration-local tenant subscription partitioning, and one-budget WebSocket startup controls (see [docs/frontend-models.md](docs/frontend-models.md), [docs/remote-request-context.md](docs/remote-request-context.md), and [docs/websocket-channels.md](docs/websocket-channels.md))
16
+ * Frontend-model transport for creating, updating, querying, and subscribing to query-filtered lifecycle events over HTTP/WebSocket, including committed counter-cache parent updates, structured per-attribute validation error responses, immutable per-operation remote request context, registration-local tenant subscription partitioning, and one-budget WebSocket startup controls (see [docs/frontend-models.md](docs/frontend-models.md), [docs/remote-request-context.md](docs/remote-request-context.md), and [docs/websocket-channels.md](docs/websocket-channels.md))
17
17
  * Client-side offline sync mutation logs and frontend-model optimistic queueing primitives (see the [shared-resource sync developer guide](docs/shared-resource-sync-guide.md) and [offline sync architecture](docs/offline-sync.md))
18
18
  * Declarative client sync scopes with per-scope cursors, automatic mutation tracking, opt-in durable base-version conflict replay, realtime delivery, and immutable-handle project clients whose local database state plus remote pull/replay/realtime request context stay tenant-bound through reconnect (see [docs/sync-client.md](docs/sync-client.md), [docs/remote-request-context.md](docs/remote-request-context.md), and [docs/offline-sync.md](docs/offline-sync.md))
19
19
  * Reactive `useLiveQuery(Model.where(...))` queries for default databases plus immutable-handle tenant live-query sources whose committed events and refreshes stay on the captured physical tenant (see [docs/live-queries.md](docs/live-queries.md))
@@ -2124,7 +2124,9 @@ await client.close()
2124
2124
 
2125
2125
  For long-lived Node clients, the constructor also accepts opt-in liveness options (all default off, so browser/Expo usage is unchanged): `webSocketImplementation` (inject Node's `ws`, since the global/undici WebSocket exposes neither protocol ping nor an unref-able socket), `heartbeatIntervalMs` (a ping heartbeat that drops a socket whose peer stops ponging, so a client notices a vanished server), and `unref` (unref the underlying socket so an idle connection can't keep the process alive on its own). See [docs/websocket-channels.md](docs/websocket-channels.md).
2126
2126
 
2127
- `await client.close()` is a final graceful shutdown that releases resumable server-session state; unexpected transport drops first attempt to resume that state. On a multi-worker server, the client automatically puts the prior session identity in the reconnect upgrade URL so the host can route it to its owning worker; routing is session-based and never source-IP-based. A successful resume retains the existing server-side connection and channel instances. If the server instead rejects the old session with `session-gone`, SnapReq promotes the already-established fresh session, reopens still-live one-to-one connection handles, and re-subscribes still-live channel handles. Those public handles remain usable and channel readiness resolves on the fresh session; explicitly closed handles stay closed. A channel that the server permanently closes or rejects remains terminal and is not automatically reopened; register a new listener after changing the authorization or request context. See [the WebSocket channel lifecycle guarantees](docs/websocket-channels.md#lifecycle-guarantees-phase-1b).
2127
+ `await client.close()` is a final graceful shutdown that releases resumable server-session state. Every channel subscription still tracked by the client, including a pending subscription, becomes closed and receives `onClose("client_close")`. A pending `subscription.ready` still rejects because no subscribe acknowledgement arrived, and its error identifies `client_close`. Throwing channel close callbacks do not stop the remaining handles or transport/base cleanup; after cleanup, `client.close()` rejects with the original error for one failed callback or teardown step, or with an `AggregateError` for multiple failures.
2128
+
2129
+ Unexpected transport drops first attempt to resume server-session state. On a multi-worker server, the client automatically puts the prior session identity in the reconnect upgrade URL so the host can route it to its owning worker; routing is session-based and never source-IP-based. A successful resume retains the existing server-side connection and channel instances. If the server instead rejects the old session with `session-gone`, SnapReq promotes the already-established fresh session, reopens still-live one-to-one connection handles, and re-subscribes still-live channel handles. Those public handles remain usable and channel readiness resolves on the fresh session; explicitly closed handles stay closed. A channel that the server permanently closes or rejects remains terminal and is not automatically reopened; register a new listener after changing the authorization or request context. See [the WebSocket channel lifecycle guarantees](docs/websocket-channels.md#lifecycle-guarantees-phase-1b).
2128
2130
 
2129
2131
  ## Subscribe to events
2130
2132
 
@@ -1,5 +1,7 @@
1
1
  // @ts-check
2
2
 
3
+ import {prepareCounterCacheParentUpdate, scheduleCounterCacheParentUpdate} from "./counter-cache-parent-updates.js"
4
+
3
5
  /**
4
6
  * Reactive counter-cache driven by a per-record magnitude.
5
7
  *
@@ -206,17 +208,26 @@ async function incrementParentCounter(record, definition, parentId, amount) {
206
208
 
207
209
  // Resolve through the record so an operation both owns the parent write and
208
210
  // rejects a relationship whose parent routes to another database.
209
- const db = record
210
- .queryForModel(parentModelClass)
211
- .driver
211
+ const parentQuery = record.queryForModel(parentModelClass)
212
+ const db = parentQuery.driver
212
213
  const counterColumnSql = db.quoteColumn(definition.counterColumn)
213
214
  const truncatedAmount = Math.trunc(amount)
215
+ const preparedParentUpdate = await prepareCounterCacheParentUpdate({
216
+ parentId,
217
+ parentModelClass,
218
+ parentPrimaryKey: relationship.getPrimaryKey(),
219
+ parentQuery
220
+ })
214
221
 
215
222
  await db.query(
216
223
  `UPDATE ${db.quoteTable(parentModelClass.tableName())} ` +
217
224
  `SET ${counterColumnSql} = COALESCE(${counterColumnSql}, 0) + ${truncatedAmount} ` +
218
225
  `WHERE ${db.quoteColumn(relationship.getPrimaryKey())} = ${db.quote(parentId)}`
219
226
  )
227
+ await scheduleCounterCacheParentUpdate({
228
+ preparedUpdate: preparedParentUpdate,
229
+ sourceRecord: record
230
+ })
220
231
  }
221
232
 
222
233
  /**
@@ -0,0 +1,137 @@
1
+ // @ts-check
2
+
3
+ import Logger from "../../logger.js"
4
+ import {ensureError} from "typanic"
5
+
6
+ /** @typedef {(parent: import("./index.js").default, previousParent: import("./index.js").default | undefined) => void | Promise<void>} CounterCacheParentUpdateListener */
7
+ /** @typedef {{canonicalParentModelClass: typeof import("./index.js").default, listeners: CounterCacheParentUpdateListener[], parentId: ReturnType<typeof JSON.parse>, parentPrimaryKey: string, parentQuery: import("../query/model-class-query.js").default<typeof import("./index.js").default>, previousParent: import("./index.js").default | undefined}} PreparedCounterCacheParentUpdate */
8
+
9
+ /** @type {WeakMap<typeof import("./index.js").default, Set<CounterCacheParentUpdateListener>>} */
10
+ const listenersByParentModelClass = new WeakMap()
11
+
12
+ /**
13
+ * Registers an internal listener for committed counter-cache parent updates.
14
+ * @param {typeof import("./index.js").default} parentModelClass - Parent model class.
15
+ * @param {CounterCacheParentUpdateListener} listener - Committed-parent listener.
16
+ * @returns {() => void} - Listener removal callback.
17
+ */
18
+ export function registerCounterCacheParentUpdateListener(parentModelClass, listener) {
19
+ const canonicalParentModelClass = parentModelClass.canonicalRecordMetadataModelClass()
20
+ let listeners = listenersByParentModelClass.get(canonicalParentModelClass)
21
+
22
+ if (!listeners) {
23
+ listeners = new Set()
24
+ listenersByParentModelClass.set(canonicalParentModelClass, listeners)
25
+ }
26
+
27
+ listeners.add(listener)
28
+
29
+ return () => listeners.delete(listener)
30
+ }
31
+
32
+ /**
33
+ * Captures one counter-cache parent's pre-mutation state when listeners are registered.
34
+ * @param {object} args - Parent update arguments.
35
+ * @param {ReturnType<typeof JSON.parse>} args.parentId - Parent relationship identity.
36
+ * @param {typeof import("./index.js").default} args.parentModelClass - Parent model class.
37
+ * @param {string} args.parentPrimaryKey - Parent relationship primary key.
38
+ * @param {import("../query/model-class-query.js").default<typeof import("./index.js").default>} args.parentQuery - Source-owned parent query.
39
+ * @returns {Promise<PreparedCounterCacheParentUpdate | undefined>} - Prepared delivery, or undefined when no listener is registered.
40
+ */
41
+ export async function prepareCounterCacheParentUpdate({parentId, parentModelClass, parentPrimaryKey, parentQuery}) {
42
+ const canonicalParentModelClass = parentModelClass.canonicalRecordMetadataModelClass()
43
+ const registeredListeners = listenersByParentModelClass.get(canonicalParentModelClass)
44
+
45
+ if (!registeredListeners || registeredListeners.size == 0) return
46
+ const previousParent = await parentQuery.findBy({[parentPrimaryKey]: parentId}) || undefined
47
+
48
+ return {
49
+ canonicalParentModelClass,
50
+ listeners: [...registeredListeners],
51
+ parentId,
52
+ parentPrimaryKey,
53
+ parentQuery,
54
+ previousParent
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Schedules one non-coalesced parent reload and notification on the source record's commit lifecycle.
60
+ * @param {object} args - Parent update arguments.
61
+ * @param {PreparedCounterCacheParentUpdate | undefined} args.preparedUpdate - Pre-mutation parent delivery state.
62
+ * @param {import("./index.js").default} args.sourceRecord - Source record that owns the transaction lifecycle.
63
+ * @returns {Promise<void>} - Resolves after registration or immediate delivery.
64
+ */
65
+ export async function scheduleCounterCacheParentUpdate({preparedUpdate, sourceRecord}) {
66
+ if (!preparedUpdate) return
67
+
68
+ const {canonicalParentModelClass, listeners, parentId, parentPrimaryKey, parentQuery, previousParent} = preparedUpdate
69
+
70
+ await sourceRecord.connection().afterCommit(async () => {
71
+ let parent
72
+
73
+ try {
74
+ parent = await parentQuery.findBy({[parentPrimaryKey]: parentId})
75
+ } catch (error) {
76
+ await reportCounterCacheParentUpdateError(canonicalParentModelClass._getConfiguration(), error)
77
+ return
78
+ }
79
+
80
+ if (!parent) return
81
+
82
+ for (const listener of listeners) {
83
+ try {
84
+ await listener(parent, previousParent)
85
+ } catch (error) {
86
+ await reportCounterCacheParentUpdateError(parent._getConfiguration(), error)
87
+ }
88
+ }
89
+ })
90
+ }
91
+
92
+ /**
93
+ * Reports a post-commit delivery failure without rejecting the durable source operation.
94
+ * @param {import("../../configuration.js").default} configuration - Owning configuration.
95
+ * @param {ReturnType<typeof JSON.parse>} caughtError - Reload or listener failure.
96
+ * @returns {Promise<void>} - Resolves after best-effort reporting.
97
+ */
98
+ async function reportCounterCacheParentUpdateError(configuration, caughtError) {
99
+ const error = ensureError(caughtError)
100
+ const payload = {
101
+ context: {stage: "counter-cache-parent-update-after-commit"},
102
+ error
103
+ }
104
+ /** @type {ReturnType<typeof JSON.parse>[]} */
105
+ const reportingErrors = []
106
+ let errorEvents
107
+
108
+ try {
109
+ errorEvents = configuration.getErrorEvents()
110
+ } catch (reportingError) {
111
+ reportingErrors.push(reportingError)
112
+ }
113
+
114
+ if (errorEvents) {
115
+ try {
116
+ errorEvents.emit("framework-error", payload)
117
+ } catch (reportingError) {
118
+ reportingErrors.push(reportingError)
119
+ }
120
+
121
+ try {
122
+ errorEvents.emit("all-error", {...payload, errorType: "framework-error"})
123
+ } catch (reportingError) {
124
+ reportingErrors.push(reportingError)
125
+ }
126
+ }
127
+
128
+ if (reportingErrors.length == 0) return
129
+
130
+ try {
131
+ const logger = new Logger("CounterCacheParentUpdates", {configuration})
132
+
133
+ await logger.error("Counter-cache parent update error reporting failed", {error, reportingErrors})
134
+ } catch {
135
+ console.error("Counter-cache parent update error reporting failed")
136
+ }
137
+ }
@@ -56,6 +56,7 @@ import {formatValue} from "../../utils/format-value.js"
56
56
  import {modelPrimaryKeyCacheKey, modelPrimaryKeyConditions, readModelPrimaryKeyValue, scalarModelPrimaryKey, scalarModelPrimaryKeyValue} from "../../utils/model-primary-key.js"
57
57
  import {captureCreateAuditChanges, captureUpdateAuditChanges, createAudit, createCreateAudit, createDestroyAudit, createUpdateAudit, initializeAuditing, registerAuditCallback, registerAuditing, withoutAudit} from "./auditing.js"
58
58
  import {registerMagnitudeCounterCache} from "./counter-cache-magnitude.js"
59
+ import {prepareCounterCacheParentUpdate, scheduleCounterCacheParentUpdate} from "./counter-cache-parent-updates.js"
59
60
  import {stateMachine} from "./state-machine.js"
60
61
  import ValidatorsFormat from "./validators/format.js"
61
62
  import ValidatorsLength from "./validators/length.js"
@@ -999,14 +1000,23 @@ class VelociousDatabaseRecord {
999
1000
  const parentTable = ParentModel.tableName()
1000
1001
  const childTable = ChildModel.tableName()
1001
1002
  const pkColumn = inflection.underscore(primaryKey)
1002
- const connection = record
1003
- .queryForModel(ParentModel)
1004
- .driver
1003
+ const parentQuery = record.queryForModel(ParentModel)
1004
+ const connection = parentQuery.driver
1005
1005
  const quoted = connection.quote(parentId)
1006
+ const preparedParentUpdate = await prepareCounterCacheParentUpdate({
1007
+ parentId,
1008
+ parentModelClass: ParentModel,
1009
+ parentPrimaryKey: primaryKey,
1010
+ parentQuery
1011
+ })
1006
1012
 
1007
1013
  const sql = `UPDATE ${connection.quoteTable(parentTable)} SET ${connection.quoteColumn(counterColumn)} = (SELECT COUNT(*) FROM ${connection.quoteTable(childTable)} WHERE ${connection.quoteColumn(fk)} = ${quoted}) WHERE ${connection.quoteColumn(pkColumn)} = ${quoted}`
1008
1014
 
1009
1015
  await connection.query(sql, {logName: `${ParentModel.name} Update`})
1016
+ await scheduleCounterCacheParentUpdate({
1017
+ preparedUpdate: preparedParentUpdate,
1018
+ sourceRecord: record
1019
+ })
1010
1020
  }
1011
1021
 
1012
1022
  /**
@@ -5,12 +5,14 @@ import {frontendModelResourcesWithBuiltInsForBackendProject} from "./built-in-re
5
5
  import {frontendModelResourceDefinitionIsClass} from "./resource-definition.js"
6
6
  import {serializeFrontendModelTransportValue} from "./transport-serialization.js"
7
7
  import {modelPrimaryKeyCacheKey, readModelPrimaryKeyValue} from "../utils/model-primary-key.js"
8
+ import {registerCounterCacheParentUpdateListener} from "../database/record/counter-cache-parent-updates.js"
8
9
 
9
10
  /** @typedef {{primaryKey: import("../utils/model-primary-key.js").ModelPrimaryKeyDefinition}} FrontendModelPublisherResource */
10
11
  /** @typedef {Record<string, import("./query.js").FrontendModelTransportValue>} FrontendModelDestroyAuthorizationRecord */
11
12
  /** @typedef {import("../database/record/index.js").default & {__frontendModelWebsocketAction?: "create" | "update", __frontendModelWebsocketDestroyAuthorizationRecord?: FrontendModelDestroyAuthorizationRecord, __frontendModelWebsocketPreviousIds?: Map<string, import("../utils/model-primary-key.js").ModelPrimaryKeyValue>}} FrontendModelWebsocketRecord */
12
13
 
13
14
  const modelClassesWithRegisteredHooks = new WeakSet()
15
+ const modelClassesWithRegisteredCounterCacheParentListeners = new WeakSet()
14
16
  const channelClassRegisteredConfigurations = new WeakSet()
15
17
  /** @type {WeakMap<import("../configuration.js").default, WeakMap<typeof import("../database/record/index.js").default, Map<string, FrontendModelPublisherResource>>>} */
16
18
  const publisherResourcesByConfiguration = new WeakMap()
@@ -127,6 +129,7 @@ export async function ensureFrontendModelWebsocketPublishersRegistered(configura
127
129
  if (!resourceClass.ModelClass) continue
128
130
 
129
131
  const modelClass = resourceClass.modelClass()
132
+ const canonicalModelClass = modelClass.canonicalRecordMetadataModelClass()
130
133
  const resourceConfiguration = resourceClass.resourceConfig()
131
134
  const configuredPrimaryKey = resourceConfiguration.primaryKey
132
135
  const modelPrimaryKey = modelClass.primaryKey()
@@ -140,38 +143,47 @@ export async function ensureFrontendModelWebsocketPublishersRegistered(configura
140
143
  publisherResourcesByConfiguration.set(configuration, publisherResourcesByModelClass)
141
144
  }
142
145
 
143
- let publisherResources = publisherResourcesByModelClass.get(modelClass)
146
+ let publisherResources = publisherResourcesByModelClass.get(canonicalModelClass)
144
147
 
145
148
  if (!publisherResources) {
146
149
  publisherResources = new Map()
147
- publisherResourcesByModelClass.set(modelClass, publisherResources)
150
+ publisherResourcesByModelClass.set(canonicalModelClass, publisherResources)
148
151
  }
149
152
 
150
153
  publisherResources.set(modelName, {
151
154
  primaryKey
152
155
  })
153
156
 
157
+ if (!modelClassesWithRegisteredCounterCacheParentListeners.has(canonicalModelClass)) {
158
+ modelClassesWithRegisteredCounterCacheParentListeners.add(canonicalModelClass)
159
+ registerCounterCacheParentUpdateListener(canonicalModelClass, (parent, previousParent) => {
160
+ const previousIds = previousParent ? frontendModelResourceIdentities(previousParent) : undefined
161
+
162
+ broadcastFrontendModelEvents(parent, "update", previousIds)
163
+ })
164
+ }
165
+
154
166
  // Register lifecycle hooks once per model class, not per configuration. A model class belongs to a
155
167
  // single backend project/config in production, so per-config registration only differs in tests where
156
168
  // the same model class is reachable from multiple configs — there it attaches duplicate beforeCreate/
157
169
  // afterSave/afterDestroy hooks that double-fire broadcasts (and leak across specs). The hooks read the
158
170
  // model's runtime configuration when broadcasting, so a single registration is sufficient.
159
- if (modelClassesWithRegisteredHooks.has(modelClass)) continue
171
+ if (modelClassesWithRegisteredHooks.has(canonicalModelClass)) continue
160
172
 
161
- modelClassesWithRegisteredHooks.add(modelClass)
173
+ modelClassesWithRegisteredHooks.add(canonicalModelClass)
162
174
 
163
- modelClass.beforeCreate((model) => {
175
+ canonicalModelClass.beforeCreate((model) => {
164
176
  /** @type {FrontendModelWebsocketRecord} */ (model).__frontendModelWebsocketAction = "create"
165
177
  })
166
178
 
167
- modelClass.beforeUpdate(async (model) => {
179
+ canonicalModelClass.beforeUpdate(async (model) => {
168
180
  const websocketModel = /** @type {FrontendModelWebsocketRecord} */ (model)
169
181
 
170
182
  websocketModel.__frontendModelWebsocketAction = "update"
171
183
  websocketModel.__frontendModelWebsocketPreviousIds = await frontendModelPreviousResourceIdentities(model)
172
184
  })
173
185
 
174
- modelClass.beforeDestroy(async (model) => {
186
+ canonicalModelClass.beforeDestroy(async (model) => {
175
187
  const websocketModel = /** @type {FrontendModelWebsocketRecord} */ (model)
176
188
  const persistedModel = await model
177
189
  .queryForModel(model.getModelClass())
@@ -183,7 +195,7 @@ export async function ensureFrontendModelWebsocketPublishersRegistered(configura
183
195
  websocketModel.__frontendModelWebsocketDestroyAuthorizationRecord = frontendModelDestroyAuthorizationRecord(persistedModel)
184
196
  })
185
197
 
186
- modelClass.afterSave((model) => {
198
+ canonicalModelClass.afterSave((model) => {
187
199
  const modelWithWebsocketAction = /** @type {FrontendModelWebsocketRecord} */ (model)
188
200
  const action = modelWithWebsocketAction.__frontendModelWebsocketAction
189
201
 
@@ -197,7 +209,7 @@ export async function ensureFrontendModelWebsocketPublishersRegistered(configura
197
209
  delete modelWithWebsocketAction.__frontendModelWebsocketPreviousIds
198
210
  })
199
211
 
200
- modelClass.afterDestroy((model) => {
212
+ canonicalModelClass.afterDestroy((model) => {
201
213
  const websocketModel = /** @type {FrontendModelWebsocketRecord} */ (model)
202
214
  const destroyAuthorizationRecord = websocketModel.__frontendModelWebsocketDestroyAuthorizationRecord
203
215
  const previousIds = websocketModel.__frontendModelWebsocketPreviousIds
@@ -217,7 +229,7 @@ export async function ensureFrontendModelWebsocketPublishersRegistered(configura
217
229
  * @returns {Promise<Map<string, import("../utils/model-primary-key.js").ModelPrimaryKeyValue>>} - Previous identities by resource name.
218
230
  */
219
231
  async function frontendModelPreviousResourceIdentities(model) {
220
- const publisherResources = publisherResourcesByConfiguration.get(model._getConfiguration())?.get(model.getModelClass())
232
+ const publisherResources = publisherResourcesForModel(model)
221
233
  /** @type {Map<string, import("../utils/model-primary-key.js").ModelPrimaryKeyValue>} */
222
234
  const previousIds = new Map()
223
235
 
@@ -252,7 +264,7 @@ async function frontendModelPreviousResourceIdentities(model) {
252
264
  * @returns {Map<string, import("../utils/model-primary-key.js").ModelPrimaryKeyValue>} - Identities by resource name.
253
265
  */
254
266
  function frontendModelResourceIdentities(model) {
255
- const publisherResources = publisherResourcesByConfiguration.get(model._getConfiguration())?.get(model.getModelClass())
267
+ const publisherResources = publisherResourcesForModel(model)
256
268
  /** @type {Map<string, import("../utils/model-primary-key.js").ModelPrimaryKeyValue>} */
257
269
  const identities = new Map()
258
270
 
@@ -267,6 +279,17 @@ function frontendModelResourceIdentities(model) {
267
279
  return identities
268
280
  }
269
281
 
282
+ /**
283
+ * Returns publisher resources through the backing model's canonical registry owner.
284
+ * @param {import("../database/record/index.js").default} model - Backing model instance.
285
+ * @returns {Map<string, FrontendModelPublisherResource> | undefined} - Publisher resources for the model.
286
+ */
287
+ function publisherResourcesForModel(model) {
288
+ const canonicalModelClass = model.getModelClass().canonicalRecordMetadataModelClass()
289
+
290
+ return publisherResourcesByConfiguration.get(model._getConfiguration())?.get(canonicalModelClass)
291
+ }
292
+
270
293
  /**
271
294
  * Serializes the persisted record for server-side destroy authorization. Binary values
272
295
  * use a dedicated byte-array marker because the shared transport serializer otherwise
@@ -337,7 +360,7 @@ function frontendModelResourceIdentity({model, previous = false, primaryKey}) {
337
360
  */
338
361
  function broadcastFrontendModelEvents(model, action, previousIds, destroyAuthorizationRecord) {
339
362
  const configuration = model._getConfiguration()
340
- const publisherResources = publisherResourcesByConfiguration.get(configuration)?.get(model.getModelClass())
363
+ const publisherResources = publisherResourcesForModel(model)
341
364
 
342
365
  if (!publisherResources) return
343
366
 
@@ -118,19 +118,51 @@ export default class VelociousWebsocketClient extends SnapReqWebSocketClient {
118
118
  if (this.gracefulClosePromise) return await this.gracefulClosePromise
119
119
 
120
120
  this.autoReconnect = false
121
+ const channelSubscriptions = [...this._channelSubscriptions.values()]
121
122
  const socket = this.socket
123
+
124
+ this._channelSubscriptions.clear()
125
+ const {promise: publishedClosePromise, reject: rejectPublishedClose, resolve: resolvePublishedClose} = Promise.withResolvers()
126
+
127
+ this.gracefulClosePromise = publishedClosePromise
128
+ // This internal bridge exists only for synchronous reentrancy. Its rejection
129
+ // duplicates closePromise, which remains the public error source below.
130
+ void publishedClosePromise.catch(() => {})
122
131
  const closePromise = (async () => {
123
- if (socket && socket.readyState === socket.OPEN) {
124
- await new Promise((resolve) => {
125
- socket.addEventListener("close", () => resolve(undefined), {once: true})
126
- socket.close(1000)
127
- })
132
+ /** @type {unknown[]} */
133
+ const closeErrors = []
134
+
135
+ for (const subscription of channelSubscriptions) {
136
+ try {
137
+ subscription._handleClosed("client_close")
138
+ } catch (error) {
139
+ closeErrors.push(error)
140
+ }
141
+ }
142
+
143
+ try {
144
+ if (socket && socket.readyState === socket.OPEN) {
145
+ await new Promise((resolve) => {
146
+ socket.addEventListener("close", () => resolve(undefined), {once: true})
147
+ socket.close(1000)
148
+ })
149
+ }
150
+ } catch (error) {
151
+ closeErrors.push(error)
152
+ }
153
+
154
+ try {
155
+ await super.close()
156
+ } catch (error) {
157
+ closeErrors.push(error)
128
158
  }
129
159
 
130
- await super.close()
160
+ if (closeErrors.length === 1) throw closeErrors[0]
161
+ if (closeErrors.length > 1) throw new AggregateError(closeErrors, "Failed to close WebSocket client")
131
162
  })()
132
163
 
133
164
  this.gracefulClosePromise = closePromise
165
+ void closePromise.then(resolvePublishedClose, rejectPublishedClose)
134
166
 
135
167
  try {
136
168
  await closePromise
@@ -1 +1 @@
1
- {"version":3,"file":"counter-cache-magnitude.d.ts","sourceRoot":"","sources":["../../../../src/database/record/counter-cache-magnitude.js"],"names":[],"mappings":"AAkBG,YAKG,+BAA+B,GALxB;IACR,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,CAAC,WAAW,EAAE,UAAU,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,MAAM,CAAA;CAClE,CAAiC;AAKlC,YAAgJ,qBAAqB,GAA3J;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,UAAU,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC;IAAC,WAAW,EAAE,UAAU,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,CAAA;CAAC,CAAuB;AA1BxK;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH;;;GAGG;AAEH;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAAC,UAAU,EAJ7C,cAAc,YAAY,EAAE,OAIiB,EAAE,UAAU,EAHzD,+BAGyD,GAFvD,IAAI,CAiDhB"}
1
+ {"version":3,"file":"counter-cache-magnitude.d.ts","sourceRoot":"","sources":["../../../../src/database/record/counter-cache-magnitude.js"],"names":[],"mappings":"AAoBG,YAKG,+BAA+B,GALxB;IACR,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,CAAC,WAAW,EAAE,UAAU,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,MAAM,CAAA;CAClE,CAAiC;AAKlC,YAAgJ,qBAAqB,GAA3J;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,UAAU,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC;IAAC,WAAW,EAAE,UAAU,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,CAAA;CAAC,CAAuB;AA1BxK;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH;;;GAGG;AAEH;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAAC,UAAU,EAJ7C,cAAc,YAAY,EAAE,OAIiB,EAAE,UAAU,EAHzD,+BAGyD,GAFvD,IAAI,CAiDhB"}
@@ -1,4 +1,5 @@
1
1
  // @ts-check
2
+ import { prepareCounterCacheParentUpdate, scheduleCounterCacheParentUpdate } from "./counter-cache-parent-updates.js";
2
3
  /**
3
4
  * Reactive counter-cache driven by a per-record magnitude.
4
5
  *
@@ -175,14 +176,23 @@ async function incrementParentCounter(record, definition, parentId, amount) {
175
176
  }
176
177
  // Resolve through the record so an operation both owns the parent write and
177
178
  // rejects a relationship whose parent routes to another database.
178
- const db = record
179
- .queryForModel(parentModelClass)
180
- .driver;
179
+ const parentQuery = record.queryForModel(parentModelClass);
180
+ const db = parentQuery.driver;
181
181
  const counterColumnSql = db.quoteColumn(definition.counterColumn);
182
182
  const truncatedAmount = Math.trunc(amount);
183
+ const preparedParentUpdate = await prepareCounterCacheParentUpdate({
184
+ parentId,
185
+ parentModelClass,
186
+ parentPrimaryKey: relationship.getPrimaryKey(),
187
+ parentQuery
188
+ });
183
189
  await db.query(`UPDATE ${db.quoteTable(parentModelClass.tableName())} ` +
184
190
  `SET ${counterColumnSql} = COALESCE(${counterColumnSql}, 0) + ${truncatedAmount} ` +
185
191
  `WHERE ${db.quoteColumn(relationship.getPrimaryKey())} = ${db.quote(parentId)}`);
192
+ await scheduleCounterCacheParentUpdate({
193
+ preparedUpdate: preparedParentUpdate,
194
+ sourceRecord: record
195
+ });
186
196
  }
187
197
  /**
188
198
  * Resolves the column name backing an attribute name.
@@ -203,4 +213,4 @@ function attributeNameForColumn(record, columnName) {
203
213
  const map = record.getModelClass().getAttributeNameToColumnNameMap();
204
214
  return Object.keys(map).find((attributeName) => map[attributeName] === columnName) || columnName;
205
215
  }
206
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY291bnRlci1jYWNoZS1tYWduaXR1ZGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvZGF0YWJhc2UvcmVjb3JkL2NvdW50ZXItY2FjaGUtbWFnbml0dWRlLmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLFlBQVk7QUFFWjs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQXNCRztBQUVIOzs7R0FHRztBQUVIOzs7OztHQUtHO0FBQ0gsTUFBTSxVQUFVLDZCQUE2QixDQUFDLFVBQVUsRUFBRSxVQUFVO0lBQ2xFOzsrQ0FFMkM7SUFDM0MsTUFBTSxZQUFZLEdBQUcsVUFBVSxDQUFBO0lBQy9CLE1BQU0sY0FBYyxHQUFHLG9DQUFvQyxVQUFVLENBQUMsYUFBYSxFQUFFLENBQUE7SUFFckYseUZBQXlGO0lBQ3pGLElBQUksTUFBTSxDQUFDLFNBQVMsQ0FBQyxjQUFjLENBQUMsSUFBSSxDQUFDLFlBQVksRUFBRSxjQUFjLENBQUMsSUFBSSxZQUFZLENBQUMsY0FBYyxDQUFDLEVBQUUsQ0FBQztRQUN2RyxPQUFNO0lBQ1IsQ0FBQztJQUVELFlBQVksQ0FBQyxjQUFjLENBQUMsR0FBRyxJQUFJLENBQUE7SUFFbkMsTUFBTSxVQUFVLEdBQUcsaUNBQWlDLFVBQVUsQ0FBQyxhQUFhLEVBQUUsQ0FBQTtJQUU5RSxVQUFVLENBQUMsVUFBVSxDQUFDLEtBQUssV0FBVyxNQUFNO1FBQzFDOzttREFFMkM7UUFDM0MsTUFBTSxhQUFhLEdBQUcsTUFBTSxDQUFBO1FBRTVCLGFBQWEsQ0FBQyxVQUFVLENBQUMsR0FBRyw0QkFBNEIsQ0FBQyxNQUFNLEVBQUUsVUFBVSxDQUFDLENBQUE7SUFDOUUsQ0FBQyxDQUFDLENBQUE7SUFFRixVQUFVLENBQUMsU0FBUyxDQUFDLEtBQUssV0FBVyxNQUFNO1FBQ3pDOzttREFFMkM7UUFDM0MsTUFBTSxhQUFhLEdBQUcsTUFBTSxDQUFBO1FBQzVCLE1BQU0sT0FBTyxHQUFHLDJDQUEyQyxDQUFDLENBQUMsYUFBYSxDQUFDLFVBQVUsQ0FBQyxDQUFDLENBQUE7UUFFdkYsYUFBYSxDQUFDLFVBQVUsQ0FBQyxHQUFHLElBQUksQ0FBQTtRQUVoQyxJQUFJLE9BQU8sRUFBRSxDQUFDO1lBQ1osTUFBTSwwQkFBMEIsQ0FBQyxNQUFNLEVBQUUsVUFBVSxFQUFFLE9BQU8sQ0FBQyxDQUFBO1FBQy9ELENBQUM7SUFDSCxDQUFDLENBQUMsQ0FBQTtJQUVGLFVBQVUsQ0FBQyxZQUFZLENBQUMsS0FBSyxXQUFXLE1BQU07UUFDNUMsTUFBTSxTQUFTLEdBQUcsVUFBVSxDQUFDLFNBQVMsQ0FBQyxNQUFNLENBQUMsYUFBYSxDQUFDLFVBQVUsQ0FBQyxlQUFlLENBQUMsQ0FBQyxDQUFBO1FBQ3hGLE1BQU0sUUFBUSxHQUFHLGVBQWUsQ0FBQyxNQUFNLEVBQUUsVUFBVSxDQUFDLENBQUE7UUFFcEQsSUFBSSxRQUFRLElBQUksU0FBUyxLQUFLLENBQUMsRUFBRSxDQUFDO1lBQ2hDLE1BQU0sc0JBQXNCLENBQUMsTUFBTSxFQUFFLFVBQVUsRUFBRSxRQUFRLEVBQUUsQ0FBQyxTQUFTLENBQUMsQ0FBQTtRQUN4RSxDQUFDO0lBQ0gsQ0FBQyxDQUFDLENBQUE7QUFDSixDQUFDO0FBRUQ7Ozs7Ozs7R0FPRztBQUNILFNBQVMsNEJBQTRCLENBQUMsTUFBTSxFQUFFLFVBQVU7SUFDdEQsTUFBTSxPQUFPLEdBQUcsTUFBTSxDQUFDLE9BQU8sRUFBRSxDQUFBO0lBQ2hDLE1BQU0sWUFBWSxHQUFHLHNCQUFzQixDQUFDLE1BQU0sRUFBRSxVQUFVLENBQUMsZUFBZSxDQUFDLENBQUE7SUFDL0UsTUFBTSxnQkFBZ0IsR0FBRyxNQUFNLENBQUMsYUFBYSxFQUFFLENBQUMscUJBQXFCLENBQUMsVUFBVSxDQUFDLFNBQVMsQ0FBQyxDQUFDLGFBQWEsRUFBRSxDQUFBO0lBRTNHLE1BQU0sU0FBUyxHQUFHLE1BQU0sQ0FBQyxhQUFhLENBQUMsVUFBVSxDQUFDLGVBQWUsQ0FBQyxDQUFBO0lBQ2xFLE1BQU0sU0FBUyxHQUFHLDZCQUE2QixDQUFDLE1BQU0sRUFBRSxVQUFVLENBQUMsZUFBZSxFQUFFLFlBQVksRUFBRSxPQUFPLEVBQUUsU0FBUyxDQUFDLENBQUE7SUFFckgsTUFBTSxXQUFXLEdBQUcsZUFBZSxDQUFDLE1BQU0sRUFBRSxVQUFVLENBQUMsQ0FBQTtJQUN2RCxNQUFNLFdBQVcsR0FBRyxnQkFBZ0IsSUFBSSxPQUFPLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxXQUFXLENBQUE7SUFFNUYsT0FBTztRQUNMLFlBQVksRUFBRSxVQUFVLENBQUMsU0FBUyxDQUFDLFNBQVMsQ0FBQztRQUM3QyxZQUFZLEVBQUUsVUFBVSxDQUFDLFNBQVMsQ0FBQyxTQUFTLENBQUM7UUFDN0MsV0FBVztRQUNYLFdBQVc7S0FDWixDQUFBO0FBQ0gsQ0FBQztBQUVEOzs7Ozs7R0FNRztBQUNILEtBQUssVUFBVSwwQkFBMEIsQ0FBQyxNQUFNLEVBQUUsVUFBVSxFQUFFLE9BQU87SUFDbkUsTUFBTSxFQUFDLFlBQVksRUFBRSxZQUFZLEVBQUUsV0FBVyxFQUFFLFdBQVcsRUFBQyxHQUFHLE9BQU8sQ0FBQTtJQUV0RSxJQUFJLFdBQVcsS0FBSyxXQUFXLEVBQUUsQ0FBQztRQUNoQyxNQUFNLEtBQUssR0FBRyxZQUFZLEdBQUcsWUFBWSxDQUFBO1FBRXpDLElBQUksV0FBVyxJQUFJLEtBQUssS0FBSyxDQUFDLEVBQUUsQ0FBQztZQUMvQixNQUFNLHNCQUFzQixDQUFDLE1BQU0sRUFBRSxVQUFVLEVBQUUsV0FBVyxFQUFFLEtBQUssQ0FBQyxDQUFBO1FBQ3RFLENBQUM7UUFFRCxPQUFNO0lBQ1IsQ0FBQztJQUVELCtFQUErRTtJQUMvRSxtQ0FBbUM7SUFDbkMsSUFBSSxXQUFXLElBQUksWUFBWSxLQUFLLENBQUMsRUFBRSxDQUFDO1FBQ3RDLE1BQU0sc0JBQXNCLENBQUMsTUFBTSxFQUFFLFVBQVUsRUFBRSxXQUFXLEVBQUUsQ0FBQyxZQUFZLENBQUMsQ0FBQTtJQUM5RSxDQUFDO0lBRUQsSUFBSSxXQUFXLElBQUksWUFBWSxLQUFLLENBQUMsRUFBRSxDQUFDO1FBQ3RDLE1BQU0sc0JBQXNCLENBQUMsTUFBTSxFQUFFLFVBQVUsRUFBRSxXQUFXLEVBQUUsWUFBWSxDQUFDLENBQUE7SUFDN0UsQ0FBQztBQUNILENBQUM7QUFFRDs7Ozs7Ozs7Ozs7O0dBWUc7QUFDSCxTQUFTLDZCQUE2QixDQUFDLE1BQU0sRUFBRSxlQUFlLEVBQUUsWUFBWSxFQUFFLE9BQU8sRUFBRSxZQUFZO0lBQ2pHLElBQUksQ0FBQyxDQUFDLFlBQVksSUFBSSxPQUFPLENBQUMsRUFBRSxDQUFDO1FBQy9CLE9BQU8sWUFBWSxDQUFBO0lBQ3JCLENBQUM7SUFFRDs7K0NBRTJDO0lBQzNDLE1BQU0sYUFBYSxHQUFHLE1BQU0sQ0FBQTtJQUM1QixNQUFNLGFBQWEsR0FBRyxhQUFhLENBQUMsUUFBUSxDQUFDLFlBQVksQ0FBQyxDQUFBO0lBRTFELE9BQU8sYUFBYSxDQUFDLFFBQVEsQ0FBQyxZQUFZLENBQUMsQ0FBQTtJQUUzQyxJQUFJLENBQUM7UUFDSCxPQUFPLE1BQU0sQ0FBQyxhQUFhLENBQUMsZUFBZSxDQUFDLENBQUE7SUFDOUMsQ0FBQztZQUFTLENBQUM7UUFDVCxhQUFhLENBQUMsUUFBUSxDQUFDLFlBQVksQ0FBQyxHQUFHLGFBQWEsQ0FBQTtJQUN0RCxDQUFDO0FBQ0gsQ0FBQztBQUVEOzs7OztHQUtHO0FBQ0gsU0FBUyxlQUFlLENBQUMsTUFBTSxFQUFFLFVBQVU7SUFDekMsTUFBTSxnQkFBZ0IsR0FBRyxNQUFNLENBQUMsYUFBYSxFQUFFLENBQUMscUJBQXFCLENBQUMsVUFBVSxDQUFDLFNBQVMsQ0FBQyxDQUFDLGFBQWEsRUFBRSxDQUFBO0lBRTNHLE9BQU8sTUFBTSxDQUFDLGFBQWEsQ0FBQyxzQkFBc0IsQ0FBQyxNQUFNLEVBQUUsZ0JBQWdCLENBQUMsQ0FBQyxDQUFBO0FBQy9FLENBQUM7QUFFRDs7Ozs7OztHQU9HO0FBQ0gsS0FBSyxVQUFVLHNCQUFzQixDQUFDLE1BQU0sRUFBRSxVQUFVLEVBQUUsUUFBUSxFQUFFLE1BQU07SUFDeEUsTUFBTSxVQUFVLEdBQUcsTUFBTSxDQUFDLGFBQWEsRUFBRSxDQUFBO0lBQ3pDLE1BQU0sWUFBWSxHQUFHLFVBQVUsQ0FBQyxxQkFBcUIsQ0FBQyxVQUFVLENBQUMsU0FBUyxDQUFDLENBQUE7SUFDM0UsTUFBTSxnQkFBZ0IsR0FBRyxZQUFZLENBQUMsbUJBQW1CLEVBQUUsQ0FBQTtJQUUzRCxJQUFJLENBQUMsZ0JBQWdCLEVBQUUsQ0FBQztRQUN0QixNQUFNLElBQUksS0FBSyxDQUFDLDRCQUE0QixVQUFVLENBQUMsWUFBWSxFQUFFLDJCQUEyQixVQUFVLENBQUMsU0FBUyxzQkFBc0IsQ0FBQyxDQUFBO0lBQzdJLENBQUM7SUFFRCw0RUFBNEU7SUFDNUUsa0VBQWtFO0lBQ2xFLE1BQU0sRUFBRSxHQUFHLE1BQU07U0FDZCxhQUFhLENBQUMsZ0JBQWdCLENBQUM7U0FDL0IsTUFBTSxDQUFBO0lBQ1QsTUFBTSxnQkFBZ0IsR0FBRyxFQUFFLENBQUMsV0FBVyxDQUFDLFVBQVUsQ0FBQyxhQUFhLENBQUMsQ0FBQTtJQUNqRSxNQUFNLGVBQWUsR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLE1BQU0sQ0FBQyxDQUFBO0lBRTFDLE1BQU0sRUFBRSxDQUFDLEtBQUssQ0FDWixVQUFVLEVBQUUsQ0FBQyxVQUFVLENBQUMsZ0JBQWdCLENBQUMsU0FBUyxFQUFFLENBQUMsR0FBRztRQUN4RCxPQUFPLGdCQUFnQixlQUFlLGdCQUFnQixVQUFVLGVBQWUsR0FBRztRQUNsRixTQUFTLEVBQUUsQ0FBQyxXQUFXLENBQUMsWUFBWSxDQUFDLGFBQWEsRUFBRSxDQUFDLE1BQU0sRUFBRSxDQUFDLEtBQUssQ0FBQyxRQUFRLENBQUMsRUFBRSxDQUNoRixDQUFBO0FBQ0gsQ0FBQztBQUVEOzs7OztHQUtHO0FBQ0gsU0FBUyxzQkFBc0IsQ0FBQyxNQUFNLEVBQUUsYUFBYTtJQUNuRCxPQUFPLE1BQU0sQ0FBQyxhQUFhLEVBQUUsQ0FBQywrQkFBK0IsRUFBRSxDQUFDLGFBQWEsQ0FBQyxJQUFJLGFBQWEsQ0FBQTtBQUNqRyxDQUFDO0FBRUQ7Ozs7O0dBS0c7QUFDSCxTQUFTLHNCQUFzQixDQUFDLE1BQU0sRUFBRSxVQUFVO0lBQ2hELE1BQU0sR0FBRyxHQUFHLE1BQU0sQ0FBQyxhQUFhLEVBQUUsQ0FBQywrQkFBK0IsRUFBRSxDQUFBO0lBRXBFLE9BQU8sTUFBTSxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxhQUFhLEVBQUUsRUFBRSxDQUFDLEdBQUcsQ0FBQyxhQUFhLENBQUMsS0FBSyxVQUFVLENBQUMsSUFBSSxVQUFVLENBQUE7QUFDbEcsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIEB0cy1jaGVja1xuXG4vKipcbiAqIFJlYWN0aXZlIGNvdW50ZXItY2FjaGUgZHJpdmVuIGJ5IGEgcGVyLXJlY29yZCBtYWduaXR1ZGUuXG4gKlxuICogTWFpbnRhaW5zIGEgY291bnRlciBjb2x1bW4gb24gYSBgYmVsb25nc1RvYCBwYXJlbnQgYXMgdGhlIHJ1bm5pbmcgc3VtIG9mIGVhY2hcbiAqIGNoaWxkJ3MgbWFnbml0dWRlIOKAlCBhIHNtYWxsIG51bWJlciBkZXJpdmVkIGZyb20gb25lIHNvdXJjZSBhdHRyaWJ1dGUgKGUuZy5cbiAqIGAxYCB3aGlsZSBhIGJ1aWxkJ3MgYHN0YXR1c2AgaXMgYHJ1bm5pbmdgLCBlbHNlIGAwYCkuIE9uIGV2ZXJ5IGNyZWF0ZSwgdXBkYXRlXG4gKiBhbmQgZGVzdHJveSB0aGUgY2hhbmdlIGluIG1hZ25pdHVkZSBpcyBhcHBsaWVkIHRvIHRoZSBwYXJlbnQgYXMgYSBzaW5nbGVcbiAqIGF0b21pYyBpbmNyZW1lbnQgKGBTRVQgY29sID0gY29sICsgZGVsdGFgKSwgYW5kIHdoZW4gdGhlIGZvcmVpZ24ga2V5IGNoYW5nZXNcbiAqIHRoZSBtYWduaXR1ZGUgaXMgbW92ZWQgZnJvbSB0aGUgb2xkIHBhcmVudCB0byB0aGUgbmV3IG9uZS5cbiAqXG4gKiBCZWNhdXNlIHRoZSBjb3VudGVyIGlzIGRlcml2ZWQgZnJvbSB0aGUgc291cmNlIGF0dHJpYnV0ZSBhbmQgZGlmZmVkIG9uIGV2ZXJ5XG4gKiB3cml0ZSwgaXQgZm9sbG93cyB0aGF0IGF0dHJpYnV0ZSBhdXRvbWF0aWNhbGx5IG5vIG1hdHRlciB3aGljaCBjb2RlIHBhdGggd3JvdGVcbiAqIGl0IOKAlCB0aGVyZSBpcyBubyBwZXItdHJhbnNpdGlvbiBpbmNyZW1lbnQvZGVjcmVtZW50IHRvIGZvcmdldC4gVGhlIG9sZCB2YWx1ZSBpc1xuICogY2FwdHVyZWQgaW4gYGJlZm9yZVNhdmVgIChWZWxvY2lvdXMgY2xlYXJzIGBjaGFuZ2VzKClgIGR1cmluZyB0aGUgcG9zdC11cGRhdGVcbiAqIHJlbG9hZCkgYW5kIGNvbnN1bWVkIGluIGBhZnRlclNhdmVgLCBzbyB0aGUgaW5jcmVtZW50IGNvbW1pdHMgYXRvbWljYWxseSB3aXRoXG4gKiB0aGUgcm93IGl0IHJlZmxlY3RzLlxuICogQHR5cGVkZWYge3tcbiAqICAgYmVsb25nc1RvOiBzdHJpbmcsXG4gKiAgIGNvdW50ZXJDb2x1bW46IHN0cmluZyxcbiAqICAgc291cmNlQXR0cmlidXRlOiBzdHJpbmcsXG4gKiAgIG1hZ25pdHVkZTogKHNvdXJjZVZhbHVlOiBSZXR1cm5UeXBlPHR5cGVvZiBKU09OLnBhcnNlPikgPT4gbnVtYmVyXG4gKiB9fSBNYWduaXR1ZGVDb3VudGVyQ2FjaGVEZWZpbml0aW9uXG4gKi9cblxuLyoqXG4gKiBDYXB0dXJlZCBwZW5kaW5nIG1hZ25pdHVkZSBjaGFuZ2Ugc3Rhc2hlZCBvbiBhIHJlY29yZCBiZXR3ZWVuIGJlZm9yZVNhdmUgYW5kIGFmdGVyU2F2ZS5cbiAqIEB0eXBlZGVmIHt7bmV3TWFnbml0dWRlOiBudW1iZXIsIG9sZE1hZ25pdHVkZTogbnVtYmVyLCBuZXdQYXJlbnRJZDogUmV0dXJuVHlwZTx0eXBlb2YgSlNPTi5wYXJzZT4sIG9sZFBhcmVudElkOiBSZXR1cm5UeXBlPHR5cGVvZiBKU09OLnBhcnNlPn19IFBlbmRpbmdNYWduaXR1ZGVEZWx0YVxuICovXG5cbi8qKlxuICogUmVnaXN0ZXJzIGEgcmVhY3RpdmUgbWFnbml0dWRlIGNvdW50ZXItY2FjaGUgb24gYSBtb2RlbCBjbGFzcy5cbiAqIEBwYXJhbSB7dHlwZW9mIGltcG9ydChcIi4vaW5kZXguanNcIikuZGVmYXVsdH0gbW9kZWxDbGFzcyAtIE1vZGVsIGNsYXNzIHRvIGFkZCB0aGUgY291bnRlciBjYWNoZSB0by5cbiAqIEBwYXJhbSB7TWFnbml0dWRlQ291bnRlckNhY2hlRGVmaW5pdGlvbn0gZGVmaW5pdGlvbiAtIENvdW50ZXIgY2FjaGUgZGVmaW5pdGlvbi5cbiAqIEByZXR1cm5zIHt2b2lkfVxuICovXG5leHBvcnQgZnVuY3Rpb24gcmVnaXN0ZXJNYWduaXR1ZGVDb3VudGVyQ2FjaGUobW9kZWxDbGFzcywgZGVmaW5pdGlvbikge1xuICAvKipcbiAgICogRHluYW1pYyBjbGFzcy5cbiAgICogQHR5cGUge1JldHVyblR5cGU8dHlwZW9mIEpTT04ucGFyc2U+fSAqL1xuICBjb25zdCBkeW5hbWljQ2xhc3MgPSBtb2RlbENsYXNzXG4gIGNvbnN0IHJlZ2lzdGVyZWRGbGFnID0gYF9tYWduaXR1ZGVDb3VudGVyQ2FjaGVSZWdpc3RlcmVkXyR7ZGVmaW5pdGlvbi5jb3VudGVyQ29sdW1ufWBcblxuICAvLyBJZGVtcG90ZW50IHBlciBjb3VudGVyIGNvbHVtbjogcmUtZGVjbGFyaW5nIChvciBzdWJjbGFzc2luZykgbXVzdCBub3QgZG91YmxlLXJlZ2lzdGVyLlxuICBpZiAoT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKGR5bmFtaWNDbGFzcywgcmVnaXN0ZXJlZEZsYWcpICYmIGR5bmFtaWNDbGFzc1tyZWdpc3RlcmVkRmxhZ10pIHtcbiAgICByZXR1cm5cbiAgfVxuXG4gIGR5bmFtaWNDbGFzc1tyZWdpc3RlcmVkRmxhZ10gPSB0cnVlXG5cbiAgY29uc3QgcGVuZGluZ0tleSA9IGBfbWFnbml0dWRlQ291bnRlckNhY2hlUGVuZGluZ18ke2RlZmluaXRpb24uY291bnRlckNvbHVtbn1gXG5cbiAgbW9kZWxDbGFzcy5iZWZvcmVTYXZlKGFzeW5jIGZ1bmN0aW9uIChyZWNvcmQpIHtcbiAgICAvKipcbiAgICAgKiBEeW5hbWljIHJlY29yZC5cbiAgICAgKiBAdHlwZSB7UmV0dXJuVHlwZTx0eXBlb2YgSlNPTi5wYXJzZT59ICovXG4gICAgY29uc3QgZHluYW1pY1JlY29yZCA9IHJlY29yZFxuXG4gICAgZHluYW1pY1JlY29yZFtwZW5kaW5nS2V5XSA9IGNvbXB1dGVQZW5kaW5nTWFnbml0dWRlRGVsdGEocmVjb3JkLCBkZWZpbml0aW9uKVxuICB9KVxuXG4gIG1vZGVsQ2xhc3MuYWZ0ZXJTYXZlKGFzeW5jIGZ1bmN0aW9uIChyZWNvcmQpIHtcbiAgICAvKipcbiAgICAgKiBEeW5hbWljIHJlY29yZC5cbiAgICAgKiBAdHlwZSB7UmV0dXJuVHlwZTx0eXBlb2YgSlNPTi5wYXJzZT59ICovXG4gICAgY29uc3QgZHluYW1pY1JlY29yZCA9IHJlY29yZFxuICAgIGNvbnN0IHBlbmRpbmcgPSAvKiogQHR5cGUge1BlbmRpbmdNYWduaXR1ZGVEZWx0YSB8IG51bGx9ICovIChkeW5hbWljUmVjb3JkW3BlbmRpbmdLZXldKVxuXG4gICAgZHluYW1pY1JlY29yZFtwZW5kaW5nS2V5XSA9IG51bGxcblxuICAgIGlmIChwZW5kaW5nKSB7XG4gICAgICBhd2FpdCBhcHBseVBlbmRpbmdNYWduaXR1ZGVEZWx0YShyZWNvcmQsIGRlZmluaXRpb24sIHBlbmRpbmcpXG4gICAgfVxuICB9KVxuXG4gIG1vZGVsQ2xhc3MuYWZ0ZXJEZXN0cm95KGFzeW5jIGZ1bmN0aW9uIChyZWNvcmQpIHtcbiAgICBjb25zdCBtYWduaXR1ZGUgPSBkZWZpbml0aW9uLm1hZ25pdHVkZShyZWNvcmQucmVhZEF0dHJpYnV0ZShkZWZpbml0aW9uLnNvdXJjZUF0dHJpYnV0ZSkpXG4gICAgY29uc3QgcGFyZW50SWQgPSBjdXJyZW50UGFyZW50SWQocmVjb3JkLCBkZWZpbml0aW9uKVxuXG4gICAgaWYgKHBhcmVudElkICYmIG1hZ25pdHVkZSAhPT0gMCkge1xuICAgICAgYXdhaXQgaW5jcmVtZW50UGFyZW50Q291bnRlcihyZWNvcmQsIGRlZmluaXRpb24sIHBhcmVudElkLCAtbWFnbml0dWRlKVxuICAgIH1cbiAgfSlcbn1cblxuLyoqXG4gKiBEaWZmcyB0aGUgc291cmNlIGF0dHJpYnV0ZSAoYW5kIGZvcmVpZ24ga2V5KSBhY3Jvc3MgdGhlIHBlbmRpbmcgc2F2ZSB0byBjYXB0dXJlXG4gKiBob3cgdGhlIHBhcmVudCBjb3VudGVyIHNob3VsZCBtb3ZlLiBSdW5zIGluIGJlZm9yZVNhdmUsIHdoZXJlIGBjaGFuZ2VzKClgIHN0aWxsXG4gKiBob2xkcyB0aGUgcHJlLXNhdmUgdmFsdWVzLlxuICogQHBhcmFtIHtpbXBvcnQoXCIuL2luZGV4LmpzXCIpLmRlZmF1bHR9IHJlY29yZCAtIFJlY29yZCBiZWluZyBzYXZlZC5cbiAqIEBwYXJhbSB7TWFnbml0dWRlQ291bnRlckNhY2hlRGVmaW5pdGlvbn0gZGVmaW5pdGlvbiAtIENvdW50ZXIgY2FjaGUgZGVmaW5pdGlvbi5cbiAqIEByZXR1cm5zIHtQZW5kaW5nTWFnbml0dWRlRGVsdGF9IFRoZSBjYXB0dXJlZCBtYWduaXR1ZGUgY2hhbmdlLlxuICovXG5mdW5jdGlvbiBjb21wdXRlUGVuZGluZ01hZ25pdHVkZURlbHRhKHJlY29yZCwgZGVmaW5pdGlvbikge1xuICBjb25zdCBjaGFuZ2VzID0gcmVjb3JkLmNoYW5nZXMoKVxuICBjb25zdCBzb3VyY2VDb2x1bW4gPSBjb2x1bW5OYW1lRm9yQXR0cmlidXRlKHJlY29yZCwgZGVmaW5pdGlvbi5zb3VyY2VBdHRyaWJ1dGUpXG4gIGNvbnN0IGZvcmVpZ25LZXlDb2x1bW4gPSByZWNvcmQuZ2V0TW9kZWxDbGFzcygpLmdldFJlbGF0aW9uc2hpcEJ5TmFtZShkZWZpbml0aW9uLmJlbG9uZ3NUbykuZ2V0Rm9yZWlnbktleSgpXG5cbiAgY29uc3QgbmV3U291cmNlID0gcmVjb3JkLnJlYWRBdHRyaWJ1dGUoZGVmaW5pdGlvbi5zb3VyY2VBdHRyaWJ1dGUpXG4gIGNvbnN0IG9sZFNvdXJjZSA9IG9sZFNvdXJjZVZhbHVlVGhyb3VnaFJlYWRDYXN0KHJlY29yZCwgZGVmaW5pdGlvbi5zb3VyY2VBdHRyaWJ1dGUsIHNvdXJjZUNvbHVtbiwgY2hhbmdlcywgbmV3U291cmNlKVxuXG4gIGNvbnN0IG5ld1BhcmVudElkID0gY3VycmVudFBhcmVudElkKHJlY29yZCwgZGVmaW5pdGlvbilcbiAgY29uc3Qgb2xkUGFyZW50SWQgPSBmb3JlaWduS2V5Q29sdW1uIGluIGNoYW5nZXMgPyBjaGFuZ2VzW2ZvcmVpZ25LZXlDb2x1bW5dWzBdIDogbmV3UGFyZW50SWRcblxuICByZXR1cm4ge1xuICAgIG5ld01hZ25pdHVkZTogZGVmaW5pdGlvbi5tYWduaXR1ZGUobmV3U291cmNlKSxcbiAgICBvbGRNYWduaXR1ZGU6IGRlZmluaXRpb24ubWFnbml0dWRlKG9sZFNvdXJjZSksXG4gICAgbmV3UGFyZW50SWQsXG4gICAgb2xkUGFyZW50SWRcbiAgfVxufVxuXG4vKipcbiAqIEFwcGxpZXMgYSBjYXB0dXJlZCBtYWduaXR1ZGUgY2hhbmdlIHRvIHRoZSBwYXJlbnQgY291bnRlcihzKSBhcyBhdG9taWMgaW5jcmVtZW50cy5cbiAqIEBwYXJhbSB7aW1wb3J0KFwiLi9pbmRleC5qc1wiKS5kZWZhdWx0fSByZWNvcmQgLSBSZWNvcmQgdGhhdCB3YXMgc2F2ZWQuXG4gKiBAcGFyYW0ge01hZ25pdHVkZUNvdW50ZXJDYWNoZURlZmluaXRpb259IGRlZmluaXRpb24gLSBDb3VudGVyIGNhY2hlIGRlZmluaXRpb24uXG4gKiBAcGFyYW0ge1BlbmRpbmdNYWduaXR1ZGVEZWx0YX0gcGVuZGluZyAtIFRoZSBjYXB0dXJlZCBtYWduaXR1ZGUgY2hhbmdlLlxuICogQHJldHVybnMge1Byb21pc2U8dm9pZD59XG4gKi9cbmFzeW5jIGZ1bmN0aW9uIGFwcGx5UGVuZGluZ01hZ25pdHVkZURlbHRhKHJlY29yZCwgZGVmaW5pdGlvbiwgcGVuZGluZykge1xuICBjb25zdCB7bmV3TWFnbml0dWRlLCBvbGRNYWduaXR1ZGUsIG5ld1BhcmVudElkLCBvbGRQYXJlbnRJZH0gPSBwZW5kaW5nXG5cbiAgaWYgKG9sZFBhcmVudElkID09PSBuZXdQYXJlbnRJZCkge1xuICAgIGNvbnN0IGRlbHRhID0gbmV3TWFnbml0dWRlIC0gb2xkTWFnbml0dWRlXG5cbiAgICBpZiAobmV3UGFyZW50SWQgJiYgZGVsdGEgIT09IDApIHtcbiAgICAgIGF3YWl0IGluY3JlbWVudFBhcmVudENvdW50ZXIocmVjb3JkLCBkZWZpbml0aW9uLCBuZXdQYXJlbnRJZCwgZGVsdGEpXG4gICAgfVxuXG4gICAgcmV0dXJuXG4gIH1cblxuICAvLyBUaGUgZm9yZWlnbiBrZXkgbW92ZWQ6IHRha2UgdGhlIG9sZCBtYWduaXR1ZGUgb2ZmIHRoZSBvbGQgcGFyZW50IGFuZCBwdXQgdGhlXG4gIC8vIG5ldyBtYWduaXR1ZGUgb24gdGhlIG5ldyBwYXJlbnQuXG4gIGlmIChvbGRQYXJlbnRJZCAmJiBvbGRNYWduaXR1ZGUgIT09IDApIHtcbiAgICBhd2FpdCBpbmNyZW1lbnRQYXJlbnRDb3VudGVyKHJlY29yZCwgZGVmaW5pdGlvbiwgb2xkUGFyZW50SWQsIC1vbGRNYWduaXR1ZGUpXG4gIH1cblxuICBpZiAobmV3UGFyZW50SWQgJiYgbmV3TWFnbml0dWRlICE9PSAwKSB7XG4gICAgYXdhaXQgaW5jcmVtZW50UGFyZW50Q291bnRlcihyZWNvcmQsIGRlZmluaXRpb24sIG5ld1BhcmVudElkLCBuZXdNYWduaXR1ZGUpXG4gIH1cbn1cblxuLyoqXG4gKiBSZWFkcyB0aGUgcHJlLXNhdmUgdmFsdWUgb2YgdGhlIHNvdXJjZSBhdHRyaWJ1dGUgdGhyb3VnaCB0aGUgbm9ybWFsIHJlYWQvY2FzdFxuICogcGF0aCwgc28gYG1hZ25pdHVkZWAgcmVjZWl2ZXMgdGhlIG9sZCB2YWx1ZSBpbiB0aGUgc2FtZSBzaGFwZSBhcyB0aGUgbmV3IG9uZVxuICogKGUuZy4gYSBkZWNsYXJlZCBib29sZWFuIGFzIGB0cnVlYC9gZmFsc2VgLCBub3QgdGhlIHJhdyBzdG9yZWQgYDFgL2AwYCkuIERyb3BzXG4gKiB0aGUgcGVuZGluZyBjaGFuZ2Ugc28gYHJlYWRBdHRyaWJ1dGVgIGZhbGxzIGJhY2sgdG8gdGhlIGNvbW1pdHRlZCB2YWx1ZSBhbmRcbiAqIGFwcGxpZXMgdGhlIHNhbWUgY2FzdCBhIGZyZXNoIHJlYWQgd291bGQsIHRoZW4gcmVzdG9yZXMgdGhlIHBlbmRpbmcgY2hhbmdlLlxuICogQHBhcmFtIHtpbXBvcnQoXCIuL2luZGV4LmpzXCIpLmRlZmF1bHR9IHJlY29yZCAtIFJlY29yZCBiZWluZyBzYXZlZC5cbiAqIEBwYXJhbSB7c3RyaW5nfSBzb3VyY2VBdHRyaWJ1dGUgLSBTb3VyY2UgYXR0cmlidXRlIG5hbWUuXG4gKiBAcGFyYW0ge3N0cmluZ30gc291cmNlQ29sdW1uIC0gU291cmNlIGNvbHVtbiBuYW1lLlxuICogQHBhcmFtIHtSZWNvcmQ8c3RyaW5nLCBSZXR1cm5UeXBlPHR5cGVvZiBKU09OLnBhcnNlPj59IGNoYW5nZXMgLSBUaGUgcmVjb3JkJ3MgcHJlLXNhdmUgY2hhbmdlcyAoY29sdW1uLWtleWVkKS5cbiAqIEBwYXJhbSB7UmV0dXJuVHlwZTx0eXBlb2YgSlNPTi5wYXJzZT59IGN1cnJlbnRWYWx1ZSAtIFRoZSBjdXJyZW50IChuZXcpIHJlYWQgdmFsdWUsIHJldHVybmVkIHdoZW4gdGhlIHNvdXJjZSBkaWQgbm90IGNoYW5nZS5cbiAqIEByZXR1cm5zIHtSZXR1cm5UeXBlPHR5cGVvZiBKU09OLnBhcnNlPn0gVGhlIHJlYWQtY2FzdCBwcmUtc2F2ZSB2YWx1ZS5cbiAqL1xuZnVuY3Rpb24gb2xkU291cmNlVmFsdWVUaHJvdWdoUmVhZENhc3QocmVjb3JkLCBzb3VyY2VBdHRyaWJ1dGUsIHNvdXJjZUNvbHVtbiwgY2hhbmdlcywgY3VycmVudFZhbHVlKSB7XG4gIGlmICghKHNvdXJjZUNvbHVtbiBpbiBjaGFuZ2VzKSkge1xuICAgIHJldHVybiBjdXJyZW50VmFsdWVcbiAgfVxuXG4gIC8qKlxuICAgKiBEeW5hbWljIHJlY29yZC5cbiAgICogQHR5cGUge1JldHVyblR5cGU8dHlwZW9mIEpTT04ucGFyc2U+fSAqL1xuICBjb25zdCBkeW5hbWljUmVjb3JkID0gcmVjb3JkXG4gIGNvbnN0IHBlbmRpbmdDaGFuZ2UgPSBkeW5hbWljUmVjb3JkLl9jaGFuZ2VzW3NvdXJjZUNvbHVtbl1cblxuICBkZWxldGUgZHluYW1pY1JlY29yZC5fY2hhbmdlc1tzb3VyY2VDb2x1bW5dXG5cbiAgdHJ5IHtcbiAgICByZXR1cm4gcmVjb3JkLnJlYWRBdHRyaWJ1dGUoc291cmNlQXR0cmlidXRlKVxuICB9IGZpbmFsbHkge1xuICAgIGR5bmFtaWNSZWNvcmQuX2NoYW5nZXNbc291cmNlQ29sdW1uXSA9IHBlbmRpbmdDaGFuZ2VcbiAgfVxufVxuXG4vKipcbiAqIFJlYWRzIHRoZSByZWNvcmQncyBjdXJyZW50IGZvcmVpZ24ta2V5IHZhbHVlIGZvciB0aGUgY291bnRlci1jYWNoZSBwYXJlbnQuXG4gKiBAcGFyYW0ge2ltcG9ydChcIi4vaW5kZXguanNcIikuZGVmYXVsdH0gcmVjb3JkIC0gUmVjb3JkIHdob3NlIHBhcmVudCBpcyB0YXJnZXRlZC5cbiAqIEBwYXJhbSB7TWFnbml0dWRlQ291bnRlckNhY2hlRGVmaW5pdGlvbn0gZGVmaW5pdGlvbiAtIENvdW50ZXIgY2FjaGUgZGVmaW5pdGlvbi5cbiAqIEByZXR1cm5zIHtSZXR1cm5UeXBlPHR5cGVvZiBKU09OLnBhcnNlPn0gVGhlIGN1cnJlbnQgZm9yZWlnbi1rZXkgdmFsdWUuXG4gKi9cbmZ1bmN0aW9uIGN1cnJlbnRQYXJlbnRJZChyZWNvcmQsIGRlZmluaXRpb24pIHtcbiAgY29uc3QgZm9yZWlnbktleUNvbHVtbiA9IHJlY29yZC5nZXRNb2RlbENsYXNzKCkuZ2V0UmVsYXRpb25zaGlwQnlOYW1lKGRlZmluaXRpb24uYmVsb25nc1RvKS5nZXRGb3JlaWduS2V5KClcblxuICByZXR1cm4gcmVjb3JkLnJlYWRBdHRyaWJ1dGUoYXR0cmlidXRlTmFtZUZvckNvbHVtbihyZWNvcmQsIGZvcmVpZ25LZXlDb2x1bW4pKVxufVxuXG4vKipcbiAqIEF0b21pY2FsbHkgYWRkcyBgYW1vdW50YCB0byB0aGUgcGFyZW50J3MgY291bnRlciBjb2x1bW4gZm9yIG9uZSBwYXJlbnQgcm93LlxuICogQHBhcmFtIHtpbXBvcnQoXCIuL2luZGV4LmpzXCIpLmRlZmF1bHR9IHJlY29yZCAtIENoaWxkIHJlY29yZCAoZm9yIGNvbm5lY3Rpb24gKyByZWxhdGlvbnNoaXAgbWV0YWRhdGEpLlxuICogQHBhcmFtIHtNYWduaXR1ZGVDb3VudGVyQ2FjaGVEZWZpbml0aW9ufSBkZWZpbml0aW9uIC0gQ291bnRlciBjYWNoZSBkZWZpbml0aW9uLlxuICogQHBhcmFtIHtSZXR1cm5UeXBlPHR5cGVvZiBKU09OLnBhcnNlPn0gcGFyZW50SWQgLSBQYXJlbnQgcHJpbWFyeS1rZXkgdmFsdWUuXG4gKiBAcGFyYW0ge251bWJlcn0gYW1vdW50IC0gU2lnbmVkIGludGVnZXIgdG8gYWRkLlxuICogQHJldHVybnMge1Byb21pc2U8dm9pZD59XG4gKi9cbmFzeW5jIGZ1bmN0aW9uIGluY3JlbWVudFBhcmVudENvdW50ZXIocmVjb3JkLCBkZWZpbml0aW9uLCBwYXJlbnRJZCwgYW1vdW50KSB7XG4gIGNvbnN0IG1vZGVsQ2xhc3MgPSByZWNvcmQuZ2V0TW9kZWxDbGFzcygpXG4gIGNvbnN0IHJlbGF0aW9uc2hpcCA9IG1vZGVsQ2xhc3MuZ2V0UmVsYXRpb25zaGlwQnlOYW1lKGRlZmluaXRpb24uYmVsb25nc1RvKVxuICBjb25zdCBwYXJlbnRNb2RlbENsYXNzID0gcmVsYXRpb25zaGlwLmdldFRhcmdldE1vZGVsQ2xhc3MoKVxuXG4gIGlmICghcGFyZW50TW9kZWxDbGFzcykge1xuICAgIHRocm93IG5ldyBFcnJvcihgbWFnbml0dWRlQ291bnRlckNhY2hlIG9uICR7bW9kZWxDbGFzcy5nZXRNb2RlbE5hbWUoKX0gY291bGQgbm90IHJlc29sdmUgdGhlIFwiJHtkZWZpbml0aW9uLmJlbG9uZ3NUb31cIiBwYXJlbnQgbW9kZWwgY2xhc3NgKVxuICB9XG5cbiAgLy8gUmVzb2x2ZSB0aHJvdWdoIHRoZSByZWNvcmQgc28gYW4gb3BlcmF0aW9uIGJvdGggb3ducyB0aGUgcGFyZW50IHdyaXRlIGFuZFxuICAvLyByZWplY3RzIGEgcmVsYXRpb25zaGlwIHdob3NlIHBhcmVudCByb3V0ZXMgdG8gYW5vdGhlciBkYXRhYmFzZS5cbiAgY29uc3QgZGIgPSByZWNvcmRcbiAgICAucXVlcnlGb3JNb2RlbChwYXJlbnRNb2RlbENsYXNzKVxuICAgIC5kcml2ZXJcbiAgY29uc3QgY291bnRlckNvbHVtblNxbCA9IGRiLnF1b3RlQ29sdW1uKGRlZmluaXRpb24uY291bnRlckNvbHVtbilcbiAgY29uc3QgdHJ1bmNhdGVkQW1vdW50ID0gTWF0aC50cnVuYyhhbW91bnQpXG5cbiAgYXdhaXQgZGIucXVlcnkoXG4gICAgYFVQREFURSAke2RiLnF1b3RlVGFibGUocGFyZW50TW9kZWxDbGFzcy50YWJsZU5hbWUoKSl9IGAgK1xuICAgIGBTRVQgJHtjb3VudGVyQ29sdW1uU3FsfSA9IENPQUxFU0NFKCR7Y291bnRlckNvbHVtblNxbH0sIDApICsgJHt0cnVuY2F0ZWRBbW91bnR9IGAgK1xuICAgIGBXSEVSRSAke2RiLnF1b3RlQ29sdW1uKHJlbGF0aW9uc2hpcC5nZXRQcmltYXJ5S2V5KCkpfSA9ICR7ZGIucXVvdGUocGFyZW50SWQpfWBcbiAgKVxufVxuXG4vKipcbiAqIFJlc29sdmVzIHRoZSBjb2x1bW4gbmFtZSBiYWNraW5nIGFuIGF0dHJpYnV0ZSBuYW1lLlxuICogQHBhcmFtIHtpbXBvcnQoXCIuL2luZGV4LmpzXCIpLmRlZmF1bHR9IHJlY29yZCAtIFJlY29yZC5cbiAqIEBwYXJhbSB7c3RyaW5nfSBhdHRyaWJ1dGVOYW1lIC0gQXR0cmlidXRlIG5hbWUuXG4gKiBAcmV0dXJucyB7c3RyaW5nfSBUaGUgY29sdW1uIG5hbWUgZm9yIHRoZSBhdHRyaWJ1dGUgKGZhbGxzIGJhY2sgdG8gdGhlIGF0dHJpYnV0ZSBuYW1lKS5cbiAqL1xuZnVuY3Rpb24gY29sdW1uTmFtZUZvckF0dHJpYnV0ZShyZWNvcmQsIGF0dHJpYnV0ZU5hbWUpIHtcbiAgcmV0dXJuIHJlY29yZC5nZXRNb2RlbENsYXNzKCkuZ2V0QXR0cmlidXRlTmFtZVRvQ29sdW1uTmFtZU1hcCgpW2F0dHJpYnV0ZU5hbWVdIHx8IGF0dHJpYnV0ZU5hbWVcbn1cblxuLyoqXG4gKiBSZXNvbHZlcyB0aGUgYXR0cmlidXRlIG5hbWUgYmFja2luZyBhIGNvbHVtbiBuYW1lLlxuICogQHBhcmFtIHtpbXBvcnQoXCIuL2luZGV4LmpzXCIpLmRlZmF1bHR9IHJlY29yZCAtIFJlY29yZC5cbiAqIEBwYXJhbSB7c3RyaW5nfSBjb2x1bW5OYW1lIC0gQ29sdW1uIG5hbWUuXG4gKiBAcmV0dXJucyB7c3RyaW5nfSBUaGUgYXR0cmlidXRlIG5hbWUgZm9yIHRoZSBjb2x1bW4gKGZhbGxzIGJhY2sgdG8gdGhlIGNvbHVtbiBuYW1lKS5cbiAqL1xuZnVuY3Rpb24gYXR0cmlidXRlTmFtZUZvckNvbHVtbihyZWNvcmQsIGNvbHVtbk5hbWUpIHtcbiAgY29uc3QgbWFwID0gcmVjb3JkLmdldE1vZGVsQ2xhc3MoKS5nZXRBdHRyaWJ1dGVOYW1lVG9Db2x1bW5OYW1lTWFwKClcblxuICByZXR1cm4gT2JqZWN0LmtleXMobWFwKS5maW5kKChhdHRyaWJ1dGVOYW1lKSA9PiBtYXBbYXR0cmlidXRlTmFtZV0gPT09IGNvbHVtbk5hbWUpIHx8IGNvbHVtbk5hbWVcbn1cbiJdfQ==
216
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY291bnRlci1jYWNoZS1tYWduaXR1ZGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvZGF0YWJhc2UvcmVjb3JkL2NvdW50ZXItY2FjaGUtbWFnbml0dWRlLmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLFlBQVk7QUFFWixPQUFPLEVBQUMsK0JBQStCLEVBQUUsZ0NBQWdDLEVBQUMsTUFBTSxtQ0FBbUMsQ0FBQTtBQUVuSDs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQXNCRztBQUVIOzs7R0FHRztBQUVIOzs7OztHQUtHO0FBQ0gsTUFBTSxVQUFVLDZCQUE2QixDQUFDLFVBQVUsRUFBRSxVQUFVO0lBQ2xFOzsrQ0FFMkM7SUFDM0MsTUFBTSxZQUFZLEdBQUcsVUFBVSxDQUFBO0lBQy9CLE1BQU0sY0FBYyxHQUFHLG9DQUFvQyxVQUFVLENBQUMsYUFBYSxFQUFFLENBQUE7SUFFckYseUZBQXlGO0lBQ3pGLElBQUksTUFBTSxDQUFDLFNBQVMsQ0FBQyxjQUFjLENBQUMsSUFBSSxDQUFDLFlBQVksRUFBRSxjQUFjLENBQUMsSUFBSSxZQUFZLENBQUMsY0FBYyxDQUFDLEVBQUUsQ0FBQztRQUN2RyxPQUFNO0lBQ1IsQ0FBQztJQUVELFlBQVksQ0FBQyxjQUFjLENBQUMsR0FBRyxJQUFJLENBQUE7SUFFbkMsTUFBTSxVQUFVLEdBQUcsaUNBQWlDLFVBQVUsQ0FBQyxhQUFhLEVBQUUsQ0FBQTtJQUU5RSxVQUFVLENBQUMsVUFBVSxDQUFDLEtBQUssV0FBVyxNQUFNO1FBQzFDOzttREFFMkM7UUFDM0MsTUFBTSxhQUFhLEdBQUcsTUFBTSxDQUFBO1FBRTVCLGFBQWEsQ0FBQyxVQUFVLENBQUMsR0FBRyw0QkFBNEIsQ0FBQyxNQUFNLEVBQUUsVUFBVSxDQUFDLENBQUE7SUFDOUUsQ0FBQyxDQUFDLENBQUE7SUFFRixVQUFVLENBQUMsU0FBUyxDQUFDLEtBQUssV0FBVyxNQUFNO1FBQ3pDOzttREFFMkM7UUFDM0MsTUFBTSxh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QUFDLFlBQVksQ0FBQyxhQUFhLEVBQUUsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxLQUFLLENBQUMsUUFBUSxDQUFDLEVBQUUsQ0FDaEYsQ0FBQTtJQUNELE1BQU0sZ0NBQWdDLENBQUM7UUFDckMsY0FBYyxFQUFFLG9CQUFvQjtRQUNwQyxZQUFZLEVBQUUsTUFBTTtLQUNyQixDQUFDLENBQUE7QUFDSixDQUFDO0FBRUQ7Ozs7O0dBS0c7QUFDSCxTQUFTLHNCQUFzQixDQUFDLE1BQU0sRUFBRSxhQUFhO0lBQ25ELE9BQU8sTUFBTSxDQUFDLGFBQWEsRUFBRSxDQUFDLCtCQUErQixFQUFFLENBQUMsYUFBYSxDQUFDLElBQUksYUFBYSxDQUFBO0FBQ2pHLENBQUM7QUFFRDs7Ozs7R0FLRztBQUNILFNBQVMsc0JBQXNCLENBQUMsTUFBTSxFQUFFLFVBQVU7SUFDaEQsTUFBTSxHQUFHLEdBQUcsTUFBTSxDQUFDLGFBQWEsRUFBRSxDQUFDLCtCQUErQixFQUFFLENBQUE7SUFFcEUsT0FBTyxNQUFNLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLGFBQWEsRUFBRSxFQUFFLENBQUMsR0FBRyxDQUFDLGFBQWEsQ0FBQyxLQUFLLFVBQVUsQ0FBQyxJQUFJLFVBQVUsQ0FBQTtBQUNsRyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQHRzLWNoZWNrXG5cbmltcG9ydCB7cHJlcGFyZUNvdW50ZXJDYWNoZVBhcmVudFVwZGF0ZSwgc2NoZWR1bGVDb3VudGVyQ2FjaGVQYXJlbnRVcGRhdGV9IGZyb20gXCIuL2NvdW50ZXItY2FjaGUtcGFyZW50LXVwZGF0ZXMuanNcIlxuXG4vKipcbiAqIFJlYWN0aXZlIGNvdW50ZXItY2FjaGUgZHJpdmVuIGJ5IGEgcGVyLXJlY29yZCBtYWduaXR1ZGUuXG4gKlxuICogTWFpbnRhaW5zIGEgY291bnRlciBjb2x1bW4gb24gYSBgYmVsb25nc1RvYCBwYXJlbnQgYXMgdGhlIHJ1bm5pbmcgc3VtIG9mIGVhY2hcbiAqIGNoaWxkJ3MgbWFnbml0dWRlIOKAlCBhIHNtYWxsIG51bWJlciBkZXJpdmVkIGZyb20gb25lIHNvdXJjZSBhdHRyaWJ1dGUgKGUuZy5cbiAqIGAxYCB3aGlsZSBhIGJ1aWxkJ3MgYHN0YXR1c2AgaXMgYHJ1bm5pbmdgLCBlbHNlIGAwYCkuIE9uIGV2ZXJ5IGNyZWF0ZSwgdXBkYXRlXG4gKiBhbmQgZGVzdHJveSB0aGUgY2hhbmdlIGluIG1hZ25pdHVkZSBpcyBhcHBsaWVkIHRvIHRoZSBwYXJlbnQgYXMgYSBzaW5nbGVcbiAqIGF0b21pYyBpbmNyZW1lbnQgKGBTRVQgY29sID0gY29sICsgZGVsdGFgKSwgYW5kIHdoZW4gdGhlIGZvcmVpZ24ga2V5IGNoYW5nZXNcbiAqIHRoZSBtYWduaXR1ZGUgaXMgbW92ZWQgZnJvbSB0aGUgb2xkIHBhcmVudCB0byB0aGUgbmV3IG9uZS5cbiAqXG4gKiBCZWNhdXNlIHRoZSBjb3VudGVyIGlzIGRlcml2ZWQgZnJvbSB0aGUgc291cmNlIGF0dHJpYnV0ZSBhbmQgZGlmZmVkIG9uIGV2ZXJ5XG4gKiB3cml0ZSwgaXQgZm9sbG93cyB0aGF0IGF0dHJpYnV0ZSBhdXRvbWF0aWNhbGx5IG5vIG1hdHRlciB3aGljaCBjb2RlIHBhdGggd3JvdGVcbiAqIGl0IOKAlCB0aGVyZSBpcyBubyBwZXItdHJhbnNpdGlvbiBpbmNyZW1lbnQvZGVjcmVtZW50IHRvIGZvcmdldC4gVGhlIG9sZCB2YWx1ZSBpc1xuICogY2FwdHVyZWQgaW4gYGJlZm9yZVNhdmVgIChWZWxvY2lvdXMgY2xlYXJzIGBjaGFuZ2VzKClgIGR1cmluZyB0aGUgcG9zdC11cGRhdGVcbiAqIHJlbG9hZCkgYW5kIGNvbnN1bWVkIGluIGBhZnRlclNhdmVgLCBzbyB0aGUgaW5jcmVtZW50IGNvbW1pdHMgYXRvbWljYWxseSB3aXRoXG4gKiB0aGUgcm93IGl0IHJlZmxlY3RzLlxuICogQHR5cGVkZWYge3tcbiAqICAgYmVsb25nc1RvOiBzdHJpbmcsXG4gKiAgIGNvdW50ZXJDb2x1bW46IHN0cmluZyxcbiAqICAgc291cmNlQXR0cmlidXRlOiBzdHJpbmcsXG4gKiAgIG1hZ25pdHVkZTogKHNvdXJjZVZhbHVlOiBSZXR1cm5UeXBlPHR5cGVvZiBKU09OLnBhcnNlPikgPT4gbnVtYmVyXG4gKiB9fSBNYWduaXR1ZGVDb3VudGVyQ2FjaGVEZWZpbml0aW9uXG4gKi9cblxuLyoqXG4gKiBDYXB0dXJlZCBwZW5kaW5nIG1hZ25pdHVkZSBjaGFuZ2Ugc3Rhc2hlZCBvbiBhIHJlY29yZCBiZXR3ZWVuIGJlZm9yZVNhdmUgYW5kIGFmdGVyU2F2ZS5cbiAqIEB0eXBlZGVmIHt7bmV3TWFnbml0dWRlOiBudW1iZXIsIG9sZE1hZ25pdHVkZTogbnVtYmVyLCBuZXdQYXJlbnRJZDogUmV0dXJuVHlwZTx0eXBlb2YgSlNPTi5wYXJzZT4sIG9sZFBhcmVudElkOiBSZXR1cm5UeXBlPHR5cGVvZiBKU09OLnBhcnNlPn19IFBlbmRpbmdNYWduaXR1ZGVEZWx0YVxuICovXG5cbi8qKlxuICogUmVnaXN0ZXJzIGEgcmVhY3RpdmUgbWFnbml0dWRlIGNvdW50ZXItY2FjaGUgb24gYSBtb2RlbCBjbGFzcy5cbiAqIEBwYXJhbSB7dHlwZW9mIGltcG9ydChcIi4vaW5kZXguanNcIikuZGVmYXVsdH0gbW9kZWxDbGFzcyAtIE1vZGVsIGNsYXNzIHRvIGFkZCB0aGUgY291bnRlciBjYWNoZSB0by5cbiAqIEBwYXJhbSB7TWFnbml0dWRlQ291bnRlckNhY2hlRGVmaW5pdGlvbn0gZGVmaW5pdGlvbiAtIENvdW50ZXIgY2FjaGUgZGVmaW5pdGlvbi5cbiAqIEByZXR1cm5zIHt2b2lkfVxuICovXG5leHBvcnQgZnVuY3Rpb24gcmVnaXN0ZXJNYWduaXR1ZGVDb3VudGVyQ2FjaGUobW9kZWxDbGFzcywgZGVmaW5pdGlvbikge1xuICAvKipcbiAgICogRHluYW1pYyBjbGFzcy5cbiAgICogQHR5cGUge1JldHVyblR5cGU8dHlwZW9mIEpTT04ucGFyc2U+fSAqL1xuICBjb25zdCBkeW5hbWljQ2xhc3MgPSBtb2RlbENsYXNzXG4gIGNvbnN0IHJlZ2lzdGVyZWRGbGFnID0gYF9tYWduaXR1ZGVDb3VudGVyQ2FjaGVSZWdpc3RlcmVkXyR7ZGVmaW5pdGlvbi5jb3VudGVyQ29sdW1ufWBcblxuICAvLyBJZGVtcG90ZW50IHBlciBjb3VudGVyIGNvbHVtbjogcmUtZGVjbGFyaW5nIChvciBzdWJjbGFzc2luZykgbXVzdCBub3QgZG91YmxlLXJlZ2lzdGVyLlxuICBpZiAoT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKGR5bmFtaWNDbGFzcywgcmVnaXN0ZXJlZEZsYWcpICYmIGR5bmFtaWNDbGFzc1tyZWdpc3RlcmVkRmxhZ10pIHtcbiAgICByZXR1cm5cbiAgfVxuXG4gIGR5bmFtaWNDbGFzc1tyZWdpc3RlcmVkRmxhZ10gPSB0cnVlXG5cbiAgY29uc3QgcGVuZGluZ0tleSA9IGBfbWFnbml0dWRlQ291bnRlckNhY2hlUGVuZGluZ18ke2RlZmluaXRpb24uY291bnRlckNvbHVtbn1gXG5cbiAgbW9kZWxDbGFzcy5iZWZvcmVTYXZlKGFzeW5jIGZ1bmN0aW9uIChyZWNvcmQpIHtcbiAgICAvKipcbiAgICAgKiBEeW5hbWljIHJlY29yZC5cbiAgICAgKiBAdHlwZSB7UmV0dXJuVHlwZTx0eXBlb2YgSlNPTi5wYXJzZT59ICovXG4gICAgY29uc3QgZHluYW1pY1JlY29yZCA9IHJlY29yZFxuXG4gICAgZHluYW1pY1JlY29yZFtwZW5kaW5nS2V5XSA9IGNvbXB1dGVQZW5kaW5nTWFnbml0dWRlRGVsdGEocmVjb3JkLCBkZWZpbml0aW9uKVxuICB9KVxuXG4gIG1vZGVsQ2xhc3MuYWZ0ZXJTYXZlKGFzeW5jIGZ1bmN0aW9uIChyZWNvcmQpIHtcbiAgICAvKipcbiAgICAgKiBEeW5hbWljIHJlY29yZC5cbiAgICAgKiBAdHlwZSB7UmV0dXJuVHlwZTx0eXBlb2YgSlNPTi5wYXJzZT59ICovXG4gICAgY29uc3QgZHluYW1pY1JlY29yZCA9IHJlY29yZFxuICAgIGNvbnN0IHBlbmRpbmcgPSAvKiogQHR5cGUge1BlbmRpbmdNYWduaXR1ZGVEZWx0YSB8IG51bGx9ICovIChkeW5hbWljUmVjb3JkW3BlbmRpbmdLZXldKVxuXG4gICAgZHluYW1pY1JlY29yZFtwZW5kaW5nS2V5XSA9IG51bGxcblxuICAgIGlmIChwZW5kaW5nKSB7XG4gICAgICBhd2FpdCBhcHBseVBlbmRpbmdNYWduaXR1ZGVEZWx0YShyZWNvcmQsIGRlZmluaXRpb24sIHBlbmRpbmcpXG4gICAgfVxuICB9KVxuXG4gIG1vZGVsQ2xhc3MuYWZ0ZXJEZXN0cm95KGFzeW5jIGZ1bmN0aW9uIChyZWNvcmQpIHtcbiAgICBjb25zdCBtYWduaXR1ZGUgPSBkZWZpbml0aW9uLm1hZ25pdHVkZShyZWNvcmQucmVhZEF0dHJpYnV0ZShkZWZpbml0aW9uLnNvdXJjZUF0dHJpYnV0ZSkpXG4gICAgY29uc3QgcGFyZW50SWQgPSBjdXJyZW50UGFyZW50SWQocmVjb3JkLCBkZWZpbml0aW9uKVxuXG4gICAgaWYgKHBhcmVudElkICYmIG1hZ25pdHVkZSAhPT0gMCkge1xuICAgICAgYXdhaXQgaW5jcmVtZW50UGFyZW50Q291bnRlcihyZWNvcmQsIGRlZmluaXRpb24sIHBhcmVudElkLCAtbWFnbml0dWRlKVxuICAgIH1cbiAgfSlcbn1cblxuLyoqXG4gKiBEaWZmcyB0aGUgc291cmNlIGF0dHJpYnV0ZSAoYW5kIGZvcmVpZ24ga2V5KSBhY3Jvc3MgdGhlIHBlbmRpbmcgc2F2ZSB0byBjYXB0dXJlXG4gKiBob3cgdGhlIHBhcmVudCBjb3VudGVyIHNob3VsZCBtb3ZlLiBSdW5zIGluIGJlZm9yZVNhdmUsIHdoZXJlIGBjaGFuZ2VzKClgIHN0aWxsXG4gKiBob2xkcyB0aGUgcHJlLXNhdmUgdmFsdWVzLlxuICogQHBhcmFtIHtpbXBvcnQoXCIuL2luZGV4LmpzXCIpLmRlZmF1bHR9IHJlY29yZCAtIFJlY29yZCBiZWluZyBzYXZlZC5cbiAqIEBwYXJhbSB7TWFnbml0dWRlQ291bnRlckNhY2hlRGVmaW5pdGlvbn0gZGVmaW5pdGlvbiAtIENvdW50ZXIgY2FjaGUgZGVmaW5pdGlvbi5cbiAqIEByZXR1cm5zIHtQZW5kaW5nTWFnbml0dWRlRGVsdGF9IFRoZSBjYXB0dXJlZCBtYWduaXR1ZGUgY2hhbmdlLlxuICovXG5mdW5jdGlvbiBjb21wdXRlUGVuZGluZ01hZ25pdHVkZURlbHRhKHJlY29yZCwgZGVmaW5pdGlvbikge1xuICBjb25zdCBjaGFuZ2VzID0gcmVjb3JkLmNoYW5nZXMoKVxuICBjb25zdCBzb3VyY2VDb2x1bW4gPSBjb2x1bW5OYW1lRm9yQXR0cmlidXRlKHJlY29yZCwgZGVmaW5pdGlvbi5zb3VyY2VBdHRyaWJ1dGUpXG4gIGNvbnN0IGZvcmVpZ25LZXlDb2x1bW4gPSByZWNvcmQuZ2V0TW9kZWxDbGFzcygpLmdldFJlbGF0aW9uc2hpcEJ5TmFtZShkZWZpbml0aW9uLmJlbG9uZ3NUbykuZ2V0Rm9yZWlnbktleSgpXG5cbiAgY29uc3QgbmV3U291cmNlID0gcmVjb3JkLnJlYWRBdHRyaWJ1dGUoZGVmaW5pdGlvbi5zb3VyY2VBdHRyaWJ1dGUpXG4gIGNvbnN0IG9sZFNvdXJjZSA9IG9sZFNvdXJjZVZhbHVlVGhyb3VnaFJlYWRDYXN0KHJlY29yZCwgZGVmaW5pdGlvbi5zb3VyY2VBdHRyaWJ1dGUsIHNvdXJjZUNvbHVtbiwgY2hhbmdlcywgbmV3U291cmNlKVxuXG4gIGNvbnN0IG5ld1BhcmVudElkID0gY3VycmVudFBhcmVudElkKHJlY29yZCwgZGVmaW5pdGlvbilcbiAgY29uc3Qgb2xkUGFyZW50SWQgPSBmb3JlaWduS2V5Q29sdW1uIGluIGNoYW5nZXMgPyBjaGFuZ2VzW2ZvcmVpZ25LZXlDb2x1bW5dWzBdIDogbmV3UGFyZW50SWRcblxuICByZXR1cm4ge1xuICAgIG5ld01hZ25pdHVkZTogZGVmaW5pdGlvbi5tYWduaXR1ZGUobmV3U291cmNlKSxcbiAgICBvbGRNYWduaXR1ZGU6IGRlZmluaXRpb24ubWFnbml0dWRlKG9sZFNvdXJjZSksXG4gICAgbmV3UGFyZW50SWQsXG4gICAgb2xkUGFyZW50SWRcbiAgfVxufVxuXG4vKipcbiAqIEFwcGxpZXMgYSBjYXB0dXJlZCBtYWduaXR1ZGUgY2hhbmdlIHRvIHRoZSBwYXJlbnQgY291bnRlcihzKSBhcyBhdG9taWMgaW5jcmVtZW50cy5cbiAqIEBwYXJhbSB7aW1wb3J0KFwiLi9pbmRleC5qc1wiKS5kZWZhdWx0fSByZWNvcmQgLSBSZWNvcmQgdGhhdCB3YXMgc2F2ZWQuXG4gKiBAcGFyYW0ge01hZ25pdHVkZUNvdW50ZXJDYWNoZURlZmluaXRpb259IGRlZmluaXRpb24gLSBDb3VudGVyIGNhY2hlIGRlZmluaXRpb24uXG4gKiBAcGFyYW0ge1BlbmRpbmdNYWduaXR1ZGVEZWx0YX0gcGVuZGluZyAtIFRoZSBjYXB0dXJlZCBtYWduaXR1ZGUgY2hhbmdlLlxuICogQHJldHVybnMge1Byb21pc2U8dm9pZD59XG4gKi9cbmFzeW5jIGZ1bmN0aW9uIGFwcGx5UGVuZGluZ01hZ25pdHVkZURlbHRhKHJlY29yZCwgZGVmaW5pdGlvbiwgcGVuZGluZykge1xuICBjb25zdCB7bmV3TWFnbml0dWRlLCBvbGRNYWduaXR1ZGUsIG5ld1BhcmVudElkLCBvbGRQYXJlbnRJZH0gPSBwZW5kaW5nXG5cbiAgaWYgKG9sZFBhcmVudElkID09PSBuZXdQYXJlbnRJZCkge1xuICAgIGNvbnN0IGRlbHRhID0gbmV3TWFnbml0dWRlIC0gb2xkTWFnbml0dWRlXG5cbiAgICBpZiAobmV3UGFyZW50SWQgJiYgZGVsdGEgIT09IDApIHtcbiAgICAgIGF3YWl0IGluY3JlbWVudFBhcmVudENvdW50ZXIocmVjb3JkLCBkZWZpbml0aW9uLCBuZXdQYXJlbnRJZCwgZGVsdGEpXG4gICAgfVxuXG4gICAgcmV0dXJuXG4gIH1cblxuICAvLyBUaGUgZm9yZWlnbiBrZXkgbW92ZWQ6IHRha2UgdGhlIG9sZCBtYWduaXR1ZGUgb2ZmIHRoZSBvbGQgcGFyZW50IGFuZCBwdXQgdGhlXG4gIC8vIG5ldyBtYWduaXR1ZGUgb24gdGhlIG5ldyBwYXJlbnQuXG4gIGlmIChvbGRQYXJlbnRJZCAmJiBvbGRNYWduaXR1ZGUgIT09IDApIHtcbiAgICBhd2FpdCBpbmNyZW1lbnRQYXJlbnRDb3VudGVyKHJlY29yZCwgZGVmaW5pdGlvbiwgb2xkUGFyZW50SWQsIC1vbGRNYWduaXR1ZGUpXG4gIH1cblxuICBpZiAobmV3UGFyZW50SWQgJiYgbmV3TWFnbml0dWRlICE9PSAwKSB7XG4gICAgYXdhaXQgaW5jcmVtZW50UGFyZW50Q291bnRlcihyZWNvcmQsIGRlZmluaXRpb24sIG5ld1BhcmVudElkLCBuZXdNYWduaXR1ZGUpXG4gIH1cbn1cblxuLyoqXG4gKiBSZWFkcyB0aGUgcHJlLXNhdmUgdmFsdWUgb2YgdGhlIHNvdXJjZSBhdHRyaWJ1dGUgdGhyb3VnaCB0aGUgbm9ybWFsIHJlYWQvY2FzdFxuICogcGF0aCwgc28gYG1hZ25pdHVkZWAgcmVjZWl2ZXMgdGhlIG9sZCB2YWx1ZSBpbiB0aGUgc2FtZSBzaGFwZSBhcyB0aGUgbmV3IG9uZVxuICogKGUuZy4gYSBkZWNsYXJlZCBib29sZWFuIGFzIGB0cnVlYC9gZmFsc2VgLCBub3QgdGhlIHJhdyBzdG9yZWQgYDFgL2AwYCkuIERyb3BzXG4gKiB0aGUgcGVuZGluZyBjaGFuZ2Ugc28gYHJlYWRBdHRyaWJ1dGVgIGZhbGxzIGJhY2sgdG8gdGhlIGNvbW1pdHRlZCB2YWx1ZSBhbmRcbiAqIGFwcGxpZXMgdGhlIHNhbWUgY2FzdCBhIGZyZXNoIHJlYWQgd291bGQsIHRoZW4gcmVzdG9yZXMgdGhlIHBlbmRpbmcgY2hhbmdlLlxuICogQHBhcmFtIHtpbXBvcnQoXCIuL2luZGV4LmpzXCIpLmRlZmF1bHR9IHJlY29yZCAtIFJlY29yZCBiZWluZyBzYXZlZC5cbiAqIEBwYXJhbSB7c3RyaW5nfSBzb3VyY2VBdHRyaWJ1dGUgLSBTb3VyY2UgYXR0cmlidXRlIG5hbWUuXG4gKiBAcGFyYW0ge3N0cmluZ30gc291cmNlQ29sdW1uIC0gU291cmNlIGNvbHVtbiBuYW1lLlxuICogQHBhcmFtIHtSZWNvcmQ8c3RyaW5nLCBSZXR1cm5UeXBlPHR5cGVvZiBKU09OLnBhcnNlPj59IGNoYW5nZXMgLSBUaGUgcmVjb3JkJ3MgcHJlLXNhdmUgY2hhbmdlcyAoY29sdW1uLWtleWVkKS5cbiAqIEBwYXJhbSB7UmV0dXJuVHlwZTx0eXBlb2YgSlNPTi5wYXJzZT59IGN1cnJlbnRWYWx1ZSAtIFRoZSBjdXJyZW50IChuZXcpIHJlYWQgdmFsdWUsIHJldHVybmVkIHdoZW4gdGhlIHNvdXJjZSBkaWQgbm90IGNoYW5nZS5cbiAqIEByZXR1cm5zIHtSZXR1cm5UeXBlPHR5cGVvZiBKU09OLnBhcnNlPn0gVGhlIHJlYWQtY2FzdCBwcmUtc2F2ZSB2YWx1ZS5cbiAqL1xuZnVuY3Rpb24gb2xkU291cmNlVmFsdWVUaHJvdWdoUmVhZENhc3QocmVjb3JkLCBzb3VyY2VBdHRyaWJ1dGUsIHNvdXJjZUNvbHVtbiwgY2hhbmdlcywgY3VycmVudFZhbHVlKSB7XG4gIGlmICghKHNvdXJjZUNvbHVtbiBpbiBjaGFuZ2VzKSkge1xuICAgIHJldHVybiBjdXJyZW50VmFsdWVcbiAgfVxuXG4gIC8qKlxuICAgKiBEeW5hbWljIHJlY29yZC5cbiAgICogQHR5cGUge1JldHVyblR5cGU8dHlwZW9mIEpTT04ucGFyc2U+fSAqL1xuICBjb25zdCBkeW5hbWljUmVjb3JkID0gcmVjb3JkXG4gIGNvbnN0IHBlbmRpbmdDaGFuZ2UgPSBkeW5hbWljUmVjb3JkLl9jaGFuZ2VzW3NvdXJjZUNvbHVtbl1cblxuICBkZWxldGUgZHluYW1pY1JlY29yZC5fY2hhbmdlc1tzb3VyY2VDb2x1bW5dXG5cbiAgdHJ5IHtcbiAgICByZXR1cm4gcmVjb3JkLnJlYWRBdHRyaWJ1dGUoc291cmNlQXR0cmlidXRlKVxuICB9IGZpbmFsbHkge1xuICAgIGR5bmFtaWNSZWNvcmQuX2NoYW5nZXNbc291cmNlQ29sdW1uXSA9IHBlbmRpbmdDaGFuZ2VcbiAgfVxufVxuXG4vKipcbiAqIFJlYWRzIHRoZSByZWNvcmQncyBjdXJyZW50IGZvcmVpZ24ta2V5IHZhbHVlIGZvciB0aGUgY291bnRlci1jYWNoZSBwYXJlbnQuXG4gKiBAcGFyYW0ge2ltcG9ydChcIi4vaW5kZXguanNcIikuZGVmYXVsdH0gcmVjb3JkIC0gUmVjb3JkIHdob3NlIHBhcmVudCBpcyB0YXJnZXRlZC5cbiAqIEBwYXJhbSB7TWFnbml0dWRlQ291bnRlckNhY2hlRGVmaW5pdGlvbn0gZGVmaW5pdGlvbiAtIENvdW50ZXIgY2FjaGUgZGVmaW5pdGlvbi5cbiAqIEByZXR1cm5zIHtSZXR1cm5UeXBlPHR5cGVvZiBKU09OLnBhcnNlPn0gVGhlIGN1cnJlbnQgZm9yZWlnbi1rZXkgdmFsdWUuXG4gKi9cbmZ1bmN0aW9uIGN1cnJlbnRQYXJlbnRJZChyZWNvcmQsIGRlZmluaXRpb24pIHtcbiAgY29uc3QgZm9yZWlnbktleUNvbHVtbiA9IHJlY29yZC5nZXRNb2RlbENsYXNzKCkuZ2V0UmVsYXRpb25zaGlwQnlOYW1lKGRlZmluaXRpb24uYmVsb25nc1RvKS5nZXRGb3JlaWduS2V5KClcblxuICByZXR1cm4gcmVjb3JkLnJlYWRBdHRyaWJ1dGUoYXR0cmlidXRlTmFtZUZvckNvbHVtbihyZWNvcmQsIGZvcmVpZ25LZXlDb2x1bW4pKVxufVxuXG4vKipcbiAqIEF0b21pY2FsbHkgYWRkcyBgYW1vdW50YCB0byB0aGUgcGFyZW50J3MgY291bnRlciBjb2x1bW4gZm9yIG9uZSBwYXJlbnQgcm93LlxuICogQHBhcmFtIHtpbXBvcnQoXCIuL2luZGV4LmpzXCIpLmRlZmF1bHR9IHJlY29yZCAtIENoaWxkIHJlY29yZCAoZm9yIGNvbm5lY3Rpb24gKyByZWxhdGlvbnNoaXAgbWV0YWRhdGEpLlxuICogQHBhcmFtIHtNYWduaXR1ZGVDb3VudGVyQ2FjaGVEZWZpbml0aW9ufSBkZWZpbml0aW9uIC0gQ291bnRlciBjYWNoZSBkZWZpbml0aW9uLlxuICogQHBhcmFtIHtSZXR1cm5UeXBlPHR5cGVvZiBKU09OLnBhcnNlPn0gcGFyZW50SWQgLSBQYXJlbnQgcHJpbWFyeS1rZXkgdmFsdWUuXG4gKiBAcGFyYW0ge251bWJlcn0gYW1vdW50IC0gU2lnbmVkIGludGVnZXIgdG8gYWRkLlxuICogQHJldHVybnMge1Byb21pc2U8dm9pZD59XG4gKi9cbmFzeW5jIGZ1bmN0aW9uIGluY3JlbWVudFBhcmVudENvdW50ZXIocmVjb3JkLCBkZWZpbml0aW9uLCBwYXJlbnRJZCwgYW1vdW50KSB7XG4gIGNvbnN0IG1vZGVsQ2xhc3MgPSByZWNvcmQuZ2V0TW9kZWxDbGFzcygpXG4gIGNvbnN0IHJlbGF0aW9uc2hpcCA9IG1vZGVsQ2xhc3MuZ2V0UmVsYXRpb25zaGlwQnlOYW1lKGRlZmluaXRpb24uYmVsb25nc1RvKVxuICBjb25zdCBwYXJlbnRNb2RlbENsYXNzID0gcmVsYXRpb25zaGlwLmdldFRhcmdldE1vZGVsQ2xhc3MoKVxuXG4gIGlmICghcGFyZW50TW9kZWxDbGFzcykge1xuICAgIHRocm93IG5ldyBFcnJvcihgbWFnbml0dWRlQ291bnRlckNhY2hlIG9uICR7bW9kZWxDbGFzcy5nZXRNb2RlbE5hbWUoKX0gY291bGQgbm90IHJlc29sdmUgdGhlIFwiJHtkZWZpbml0aW9uLmJlbG9uZ3NUb31cIiBwYXJlbnQgbW9kZWwgY2xhc3NgKVxuICB9XG5cbiAgLy8gUmVzb2x2ZSB0aHJvdWdoIHRoZSByZWNvcmQgc28gYW4gb3BlcmF0aW9uIGJvdGggb3ducyB0aGUgcGFyZW50IHdyaXRlIGFuZFxuICAvLyByZWplY3RzIGEgcmVsYXRpb25zaGlwIHdob3NlIHBhcmVudCByb3V0ZXMgdG8gYW5vdGhlciBkYXRhYmFzZS5cbiAgY29uc3QgcGFyZW50UXVlcnkgPSByZWNvcmQucXVlcnlGb3JNb2RlbChwYXJlbnRNb2RlbENsYXNzKVxuICBjb25zdCBkYiA9IHBhcmVudFF1ZXJ5LmRyaXZlclxuICBjb25zdCBjb3VudGVyQ29sdW1uU3FsID0gZGIucXVvdGVDb2x1bW4oZGVmaW5pdGlvbi5jb3VudGVyQ29sdW1uKVxuICBjb25zdCB0cnVuY2F0ZWRBbW91bnQgPSBNYXRoLnRydW5jKGFtb3VudClcbiAgY29uc3QgcHJlcGFyZWRQYXJlbnRVcGRhdGUgPSBhd2FpdCBwcmVwYXJlQ291bnRlckNhY2hlUGFyZW50VXBkYXRlKHtcbiAgICBwYXJlbnRJZCxcbiAgICBwYXJlbnRNb2RlbENsYXNzLFxuICAgIHBhcmVudFByaW1hcnlLZXk6IHJlbGF0aW9uc2hpcC5nZXRQcmltYXJ5S2V5KCksXG4gICAgcGFyZW50UXVlcnlcbiAgfSlcblxuICBhd2FpdCBkYi5xdWVyeShcbiAgICBgVVBEQVRFICR7ZGIucXVvdGVUYWJsZShwYXJlbnRNb2RlbENsYXNzLnRhYmxlTmFtZSgpKX0gYCArXG4gICAgYFNFVCAke2NvdW50ZXJDb2x1bW5TcWx9ID0gQ09BTEVTQ0UoJHtjb3VudGVyQ29sdW1uU3FsfSwgMCkgKyAke3RydW5jYXRlZEFtb3VudH0gYCArXG4gICAgYFdIRVJFICR7ZGIucXVvdGVDb2x1bW4ocmVsYXRpb25zaGlwLmdldFByaW1hcnlLZXkoKSl9ID0gJHtkYi5xdW90ZShwYXJlbnRJZCl9YFxuICApXG4gIGF3YWl0IHNjaGVkdWxlQ291bnRlckNhY2hlUGFyZW50VXBkYXRlKHtcbiAgICBwcmVwYXJlZFVwZGF0ZTogcHJlcGFyZWRQYXJlbnRVcGRhdGUsXG4gICAgc291cmNlUmVjb3JkOiByZWNvcmRcbiAgfSlcbn1cblxuLyoqXG4gKiBSZXNvbHZlcyB0aGUgY29sdW1uIG5hbWUgYmFja2luZyBhbiBhdHRyaWJ1dGUgbmFtZS5cbiAqIEBwYXJhbSB7aW1wb3J0KFwiLi9pbmRleC5qc1wiKS5kZWZhdWx0fSByZWNvcmQgLSBSZWNvcmQuXG4gKiBAcGFyYW0ge3N0cmluZ30gYXR0cmlidXRlTmFtZSAtIEF0dHJpYnV0ZSBuYW1lLlxuICogQHJldHVybnMge3N0cmluZ30gVGhlIGNvbHVtbiBuYW1lIGZvciB0aGUgYXR0cmlidXRlIChmYWxscyBiYWNrIHRvIHRoZSBhdHRyaWJ1dGUgbmFtZSkuXG4gKi9cbmZ1bmN0aW9uIGNvbHVtbk5hbWVGb3JBdHRyaWJ1dGUocmVjb3JkLCBhdHRyaWJ1dGVOYW1lKSB7XG4gIHJldHVybiByZWNvcmQuZ2V0TW9kZWxDbGFzcygpLmdldEF0dHJpYnV0ZU5hbWVUb0NvbHVtbk5hbWVNYXAoKVthdHRyaWJ1dGVOYW1lXSB8fCBhdHRyaWJ1dGVOYW1lXG59XG5cbi8qKlxuICogUmVzb2x2ZXMgdGhlIGF0dHJpYnV0ZSBuYW1lIGJhY2tpbmcgYSBjb2x1bW4gbmFtZS5cbiAqIEBwYXJhbSB7aW1wb3J0KFwiLi9pbmRleC5qc1wiKS5kZWZhdWx0fSByZWNvcmQgLSBSZWNvcmQuXG4gKiBAcGFyYW0ge3N0cmluZ30gY29sdW1uTmFtZSAtIENvbHVtbiBuYW1lLlxuICogQHJldHVybnMge3N0cmluZ30gVGhlIGF0dHJpYnV0ZSBuYW1lIGZvciB0aGUgY29sdW1uIChmYWxscyBiYWNrIHRvIHRoZSBjb2x1bW4gbmFtZSkuXG4gKi9cbmZ1bmN0aW9uIGF0dHJpYnV0ZU5hbWVGb3JDb2x1bW4ocmVjb3JkLCBjb2x1bW5OYW1lKSB7XG4gIGNvbnN0IG1hcCA9IHJlY29yZC5nZXRNb2RlbENsYXNzKCkuZ2V0QXR0cmlidXRlTmFtZVRvQ29sdW1uTmFtZU1hcCgpXG5cbiAgcmV0dXJuIE9iamVjdC5rZXlzKG1hcCkuZmluZCgoYXR0cmlidXRlTmFtZSkgPT4gbWFwW2F0dHJpYnV0ZU5hbWVdID09PSBjb2x1bW5OYW1lKSB8fCBjb2x1bW5OYW1lXG59XG4iXX0=
@@ -0,0 +1,43 @@
1
+ export type CounterCacheParentUpdateListener = (parent: import("./index.js").default, previousParent: import("./index.js").default | undefined) => void | Promise<void>;
2
+ export type PreparedCounterCacheParentUpdate = {
3
+ canonicalParentModelClass: typeof import("./index.js").default;
4
+ listeners: CounterCacheParentUpdateListener[];
5
+ parentId: ReturnType<typeof JSON.parse>;
6
+ parentPrimaryKey: string;
7
+ parentQuery: import("../query/model-class-query.js").default<typeof import("./index.js").default>;
8
+ previousParent: import("./index.js").default | undefined;
9
+ };
10
+ /**
11
+ * Registers an internal listener for committed counter-cache parent updates.
12
+ * @param {typeof import("./index.js").default} parentModelClass - Parent model class.
13
+ * @param {CounterCacheParentUpdateListener} listener - Committed-parent listener.
14
+ * @returns {() => void} - Listener removal callback.
15
+ */
16
+ export declare function registerCounterCacheParentUpdateListener(parentModelClass: typeof import("./index.js").default, listener: CounterCacheParentUpdateListener): () => void;
17
+ /**
18
+ * Captures one counter-cache parent's pre-mutation state when listeners are registered.
19
+ * @param {object} args - Parent update arguments.
20
+ * @param {ReturnType<typeof JSON.parse>} args.parentId - Parent relationship identity.
21
+ * @param {typeof import("./index.js").default} args.parentModelClass - Parent model class.
22
+ * @param {string} args.parentPrimaryKey - Parent relationship primary key.
23
+ * @param {import("../query/model-class-query.js").default<typeof import("./index.js").default>} args.parentQuery - Source-owned parent query.
24
+ * @returns {Promise<PreparedCounterCacheParentUpdate | undefined>} - Prepared delivery, or undefined when no listener is registered.
25
+ */
26
+ export declare function prepareCounterCacheParentUpdate({ parentId, parentModelClass, parentPrimaryKey, parentQuery }: {
27
+ parentId: ReturnType<typeof JSON.parse>;
28
+ parentModelClass: typeof import("./index.js").default;
29
+ parentPrimaryKey: string;
30
+ parentQuery: import("../query/model-class-query.js").default<typeof import("./index.js").default>;
31
+ }): Promise<PreparedCounterCacheParentUpdate | undefined>;
32
+ /**
33
+ * Schedules one non-coalesced parent reload and notification on the source record's commit lifecycle.
34
+ * @param {object} args - Parent update arguments.
35
+ * @param {PreparedCounterCacheParentUpdate | undefined} args.preparedUpdate - Pre-mutation parent delivery state.
36
+ * @param {import("./index.js").default} args.sourceRecord - Source record that owns the transaction lifecycle.
37
+ * @returns {Promise<void>} - Resolves after registration or immediate delivery.
38
+ */
39
+ export declare function scheduleCounterCacheParentUpdate({ preparedUpdate, sourceRecord }: {
40
+ preparedUpdate: PreparedCounterCacheParentUpdate | undefined;
41
+ sourceRecord: import("./index.js").default;
42
+ }): Promise<void>;
43
+ //# sourceMappingURL=counter-cache-parent-updates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"counter-cache-parent-updates.d.ts","sourceRoot":"","sources":["../../../../src/database/record/counter-cache-parent-updates.js"],"names":[],"mappings":"AAKI,YAAoI,gCAAgC,GAA1J,CAAC,MAAM,EAAE,OAAO,YAAY,EAAE,OAAO,EAAE,cAAc,EAAE,OAAO,YAAY,EAAE,OAAO,GAAG,SAAS,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAkC;AACpK,YAA2V,gCAAgC,GAAjX;IAAC,yBAAyB,EAAE,cAAc,YAAY,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,gCAAgC,EAAE,CAAC;IAAC,QAAQ,EAAE,UAAU,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC;IAAC,gBAAgB,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,OAAO,+BAA+B,EAAE,OAAO,CAAC,cAAc,YAAY,EAAE,OAAO,CAAC,CAAC;IAAC,cAAc,EAAE,OAAO,YAAY,EAAE,OAAO,GAAG,SAAS,CAAA;CAAC,CAAkC;AAK/X;;;;;GAKG;AACH,wBAAgB,wCAAwC,CAAC,gBAAgB,EAJ9D,cAAc,YAAY,EAAE,OAIkC,EAAE,QAAQ,EAHxE,gCAGwE,GAFtE,MAAM,IAAI,CActB;AAED;;;;;;;;GAQG;AACH,wBAAsB,+BAA+B,CAAC,EAAC,QAAQ,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,WAAW,EAAC,EAN9G;IAA4C,QAAQ,EAA5C,UAAU,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,CACrC;IAAkD,gBAAgB,EAA1D,cAAc,YAAY,EAAE,OAAO,CAC3C;IAAqB,gBAAgB,EAA7B,MAAM,CACd;IAAmG,WAAW,EAAtG,OAAO,+BAA+B,EAAE,OAAO,CAAC,cAAc,YAAY,EAAE,OAAO,CAAC,CAC5F;CAE8G,GAFpG,OAAO,CAAC,gCAAgC,GAAG,SAAS,CAAC,CAiBjE;AAED;;;;;;GAMG;AACH,wBAAsB,gCAAgC,CAAC,EAAC,cAAc,EAAE,YAAY,EAAC,EAJlF;IAA2D,cAAc,EAAjE,gCAAgC,GAAG,SAAS,CACpD;IAA2C,YAAY,EAA/C,OAAO,YAAY,EAAE,OAAO,CACpC;CAEkF,GAFxE,OAAO,CAAC,IAAI,CAAC,CA2BzB"}