velocious 1.0.481 → 1.0.483
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 +2 -0
- package/build/cli/commands/db/tenants/drop.js +34 -0
- package/build/cli/tenant-database-command-helper.js +8 -85
- package/build/database/tenants/data-copier.js +269 -0
- package/build/database/tenants/schema-cloner.js +390 -0
- package/build/database/tenants/tenant-table-plan.js +20 -0
- package/build/src/cli/commands/db/tenants/drop.d.ts +13 -0
- package/build/src/cli/commands/db/tenants/drop.d.ts.map +1 -0
- package/build/src/cli/commands/db/tenants/drop.js +31 -0
- package/build/src/cli/tenant-database-command-helper.d.ts +0 -20
- package/build/src/cli/tenant-database-command-helper.d.ts.map +1 -1
- package/build/src/cli/tenant-database-command-helper.js +8 -73
- package/build/src/database/tenants/data-copier.d.ts +125 -0
- package/build/src/database/tenants/data-copier.d.ts.map +1 -0
- package/build/src/database/tenants/data-copier.js +232 -0
- package/build/src/database/tenants/schema-cloner.d.ts +147 -0
- package/build/src/database/tenants/schema-cloner.d.ts.map +1 -0
- package/build/src/database/tenants/schema-cloner.js +326 -0
- package/build/src/database/tenants/tenant-table-plan.d.ts +30 -0
- package/build/src/database/tenants/tenant-table-plan.d.ts.map +1 -0
- package/build/src/database/tenants/tenant-table-plan.js +3 -0
- package/build/src/tenants/tenant-iterator.d.ts +52 -0
- package/build/src/tenants/tenant-iterator.d.ts.map +1 -0
- package/build/src/tenants/tenant-iterator.js +97 -0
- package/build/src/tenants/tenant.d.ts +51 -0
- package/build/src/tenants/tenant.d.ts.map +1 -0
- package/build/src/tenants/tenant.js +76 -0
- package/build/tenants/tenant-iterator.js +111 -0
- package/build/tenants/tenant.js +86 -0
- package/build/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/cli/commands/db/tenants/drop.js +34 -0
- package/src/cli/tenant-database-command-helper.js +8 -85
- package/src/database/tenants/data-copier.js +269 -0
- package/src/database/tenants/schema-cloner.js +390 -0
- package/src/database/tenants/tenant-table-plan.js +20 -0
- package/src/tenants/tenant-iterator.js +111 -0
- package/src/tenants/tenant.js +86 -0
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import MigrationsLedger from "../migrations-ledger.js";
|
|
3
|
+
import TableData from "../table-data/index.js";
|
|
4
|
+
import TableIndex from "../table-data/table-index.js";
|
|
5
|
+
const SIMPLE_DEFAULT_PATTERN = /^(?:-?\d+(?:\.\d+)?|[A-Za-z0-9 _.,:/@+-]*)$/;
|
|
6
|
+
/** @type {Record<string, number>} */
|
|
7
|
+
const TEXT_TYPE_RANKS = {
|
|
8
|
+
tinytext: 1,
|
|
9
|
+
text: 2,
|
|
10
|
+
mediumtext: 3,
|
|
11
|
+
longtext: 4
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Clones table structure (columns, text-type widening, indexes) from a source database
|
|
15
|
+
* into a target database for a given set of tables, then baselines the target's
|
|
16
|
+
* `schema_migrations` ledger to the source via {@link MigrationsLedger}. This is the
|
|
17
|
+
* mechanism multi-tenant apps use to provision a tenant database from a template/global
|
|
18
|
+
* database without re-running migrations: the structure is copied and the ledger is
|
|
19
|
+
* recorded as already-applied, so a later `db:tenants:migrate` does not re-run an
|
|
20
|
+
* `addColumn` whose column already exists.
|
|
21
|
+
*
|
|
22
|
+
* The cloner is intentionally policy-free — the caller decides which tables to sync and
|
|
23
|
+
* which databases are source/target. It is idempotent: missing tables are created,
|
|
24
|
+
* missing columns added, too-narrow text columns widened, and missing indexes created;
|
|
25
|
+
* an index whose definition diverges from the source is treated as drift and throws.
|
|
26
|
+
*/
|
|
27
|
+
export default class SchemaCloner {
|
|
28
|
+
/**
|
|
29
|
+
* Creates a cloner that copies table structure from `sourceDb` into `targetDb`.
|
|
30
|
+
* @param {{sourceDb: import("../drivers/base.js").default, targetDb: import("../drivers/base.js").default}} args
|
|
31
|
+
*/
|
|
32
|
+
constructor({ sourceDb, targetDb }) {
|
|
33
|
+
this.sourceDb = sourceDb;
|
|
34
|
+
this.targetDb = targetDb;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Clones every given table from the source into the target, then baselines the
|
|
38
|
+
* target's ledger so the cloned schema is recorded as already-migrated.
|
|
39
|
+
* @param {string[]} tableNames
|
|
40
|
+
* @returns {Promise<void>}
|
|
41
|
+
*/
|
|
42
|
+
async syncTables(tableNames) {
|
|
43
|
+
for (const tableName of tableNames) {
|
|
44
|
+
await this.syncTable(tableName);
|
|
45
|
+
}
|
|
46
|
+
await this.reconcileLedger();
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Clones a single table from the source into the target, creating it or adding and
|
|
50
|
+
* widening columns and indexes as needed.
|
|
51
|
+
* @param {string} tableName
|
|
52
|
+
* @returns {Promise<void>}
|
|
53
|
+
*/
|
|
54
|
+
async syncTable(tableName) {
|
|
55
|
+
const sourceTable = await this.sourceDb.getTableByName(tableName);
|
|
56
|
+
if (!sourceTable) {
|
|
57
|
+
throw new Error(`Expected source table to exist: ${tableName}`);
|
|
58
|
+
}
|
|
59
|
+
if (!await this.targetDb.tableExists(tableName)) {
|
|
60
|
+
await this.createTargetTable({ sourceTable, tableName });
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const changedColumns = await this.ensureTargetColumns({ sourceTable, tableName });
|
|
64
|
+
if (changedColumns) {
|
|
65
|
+
this.targetDb.clearSchemaCache();
|
|
66
|
+
}
|
|
67
|
+
await this.ensureTargetIndexes({ sourceTable, tableName });
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Creates the table in the target from the source table's columns and its
|
|
71
|
+
* non-primary-key indexes.
|
|
72
|
+
* @param {{sourceTable: import("../drivers/base-table.js").default, tableName: string}} args
|
|
73
|
+
* @returns {Promise<void>}
|
|
74
|
+
*/
|
|
75
|
+
async createTargetTable({ sourceTable, tableName }) {
|
|
76
|
+
const tableData = new TableData(tableName);
|
|
77
|
+
for (const sourceColumn of await sourceTable.getColumns()) {
|
|
78
|
+
tableData.addColumn(sourceColumn.getName(), this.columnArgsFromSourceColumn(sourceColumn, { isNewColumn: false }));
|
|
79
|
+
}
|
|
80
|
+
for (const sourceIndex of await sourceTable.getIndexes()) {
|
|
81
|
+
if (!sourceIndex.isPrimaryKey()) {
|
|
82
|
+
tableData.addIndex(this.tableDataIndexFromSourceIndex(sourceIndex));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
await this.targetDb.createTable(tableData);
|
|
86
|
+
this.targetDb.clearSchemaCache();
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Adds columns present on the source but missing from the target, and widens
|
|
90
|
+
* too-narrow target text columns.
|
|
91
|
+
* @param {{sourceTable: import("../drivers/base-table.js").default, tableName: string}} args
|
|
92
|
+
* @returns {Promise<boolean>} Whether any column was added or widened.
|
|
93
|
+
*/
|
|
94
|
+
async ensureTargetColumns({ sourceTable, tableName }) {
|
|
95
|
+
const sourceColumns = await sourceTable.getColumns();
|
|
96
|
+
const targetTable = await this.targetDb.getTableByNameOrFail(tableName);
|
|
97
|
+
const targetColumnsByName = new Map();
|
|
98
|
+
const missingColumns = [];
|
|
99
|
+
const columnsNeedingWidening = [];
|
|
100
|
+
for (const targetColumn of await targetTable.getColumns()) {
|
|
101
|
+
targetColumnsByName.set(targetColumn.getName(), targetColumn);
|
|
102
|
+
}
|
|
103
|
+
for (const sourceColumn of sourceColumns) {
|
|
104
|
+
const targetColumn = targetColumnsByName.get(sourceColumn.getName());
|
|
105
|
+
if (!targetColumn) {
|
|
106
|
+
missingColumns.push(sourceColumn);
|
|
107
|
+
}
|
|
108
|
+
else if (this.columnNeedsWidening(sourceColumn, targetColumn)) {
|
|
109
|
+
columnsNeedingWidening.push(sourceColumn);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (missingColumns.length <= 0 && columnsNeedingWidening.length <= 0) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
const tableData = new TableData(tableName);
|
|
116
|
+
for (const sourceColumn of missingColumns) {
|
|
117
|
+
tableData.addColumn(sourceColumn.getName(), this.columnArgsFromSourceColumn(sourceColumn, { isNewColumn: true }));
|
|
118
|
+
}
|
|
119
|
+
for (const sourceColumn of columnsNeedingWidening) {
|
|
120
|
+
tableData.addColumn(sourceColumn.getName(), this.columnArgsFromSourceColumn(sourceColumn, { isNewColumn: false }));
|
|
121
|
+
}
|
|
122
|
+
for (const alterSql of await this.targetDb.alterTableSQLs(tableData)) {
|
|
123
|
+
await this.targetDb.query(alterSql);
|
|
124
|
+
}
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Creates non-primary-key indexes present on the source but missing from the target,
|
|
129
|
+
* and throws if an existing target index diverges from the source.
|
|
130
|
+
* @param {{sourceTable: import("../drivers/base-table.js").default, tableName: string}} args
|
|
131
|
+
* @returns {Promise<void>}
|
|
132
|
+
*/
|
|
133
|
+
async ensureTargetIndexes({ sourceTable, tableName }) {
|
|
134
|
+
const targetTable = await this.targetDb.getTableByNameOrFail(tableName);
|
|
135
|
+
/** @type {Map<string, import("../drivers/base-columns-index.js").default>} */
|
|
136
|
+
const targetIndexesByName = new Map();
|
|
137
|
+
const targetIndexSignatures = new Set();
|
|
138
|
+
let createdIndex = false;
|
|
139
|
+
for (const targetIndex of await targetTable.getIndexes()) {
|
|
140
|
+
targetIndexesByName.set(targetIndex.getName(), targetIndex);
|
|
141
|
+
targetIndexSignatures.add(this.indexSignature(targetIndex));
|
|
142
|
+
}
|
|
143
|
+
for (const sourceIndex of await sourceTable.getIndexes()) {
|
|
144
|
+
if (sourceIndex.isPrimaryKey()) {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
const sourceIndexSignature = this.indexSignature(sourceIndex);
|
|
148
|
+
// SQLite index names are unique per-database, not per-table, so match cloned
|
|
149
|
+
// indexes by their column/uniqueness signature rather than their name.
|
|
150
|
+
if (this.targetDb.getType() === "sqlite" && targetIndexSignatures.has(sourceIndexSignature)) {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
const targetIndex = this.targetDb.getType() === "sqlite" ? undefined : targetIndexesByName.get(sourceIndex.getName());
|
|
154
|
+
if (!targetIndex) {
|
|
155
|
+
const createIndexSqls = await this.targetDb.createIndexSQLs(this.createIndexArgsFromSourceIndex({ sourceIndex, tableName }));
|
|
156
|
+
for (const createIndexSql of createIndexSqls) {
|
|
157
|
+
await this.targetDb.query(createIndexSql);
|
|
158
|
+
}
|
|
159
|
+
createdIndex = true;
|
|
160
|
+
targetIndexSignatures.add(sourceIndexSignature);
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (!this.indexesMatch(sourceIndex, targetIndex)) {
|
|
164
|
+
throw new Error(`Schema clone index drift for ${tableName}.${sourceIndex.getName()}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (createdIndex) {
|
|
168
|
+
this.targetDb.clearSchemaCache();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Baselines the target ledger so the cloned schema is recorded as already-applied.
|
|
173
|
+
* @returns {Promise<string[]>} The versions newly recorded on the target.
|
|
174
|
+
*/
|
|
175
|
+
async reconcileLedger() {
|
|
176
|
+
return await MigrationsLedger.baselineFromDatabase({ sourceDb: this.sourceDb, targetDb: this.targetDb });
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Whether the target ledger is missing any version applied on the source — i.e. the
|
|
180
|
+
* target schema may have been advanced out of band without recording it.
|
|
181
|
+
* @returns {Promise<boolean>}
|
|
182
|
+
*/
|
|
183
|
+
async ledgerDriftsFromSource() {
|
|
184
|
+
if (!await MigrationsLedger.tableExists(this.targetDb)) {
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
const sourceVersions = await MigrationsLedger.appliedVersions(this.sourceDb);
|
|
188
|
+
const targetVersionSet = new Set(await MigrationsLedger.appliedVersions(this.targetDb));
|
|
189
|
+
return sourceVersions.some((version) => !targetVersionSet.has(version));
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Maps a source index into a TableData index for table creation (SQLite omits the
|
|
193
|
+
* index name so the driver can generate a unique one).
|
|
194
|
+
* @param {import("../drivers/base-columns-index.js").default} sourceIndex
|
|
195
|
+
* @returns {TableIndex}
|
|
196
|
+
*/
|
|
197
|
+
tableDataIndexFromSourceIndex(sourceIndex) {
|
|
198
|
+
/** @type {{name?: string, unique: boolean}} */
|
|
199
|
+
const args = { unique: sourceIndex.isUnique() };
|
|
200
|
+
// SQLite index names are unique per-database, not per-table, so let the driver
|
|
201
|
+
// generate one; other drivers preserve the source index name. Build the TableIndex
|
|
202
|
+
// directly (rather than via the driver's getTableDataIndex, which only MySQL and
|
|
203
|
+
// SQLite implement) so cloning a PostgreSQL or MS-SQL source table works too.
|
|
204
|
+
if (this.targetDb.getType() !== "sqlite") {
|
|
205
|
+
args.name = sourceIndex.getName();
|
|
206
|
+
}
|
|
207
|
+
return new TableIndex(sourceIndex.getColumnNames(), args);
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Builds driver create-index args from a source index (the index name is omitted on
|
|
211
|
+
* SQLite, where index names are unique per-database rather than per-table).
|
|
212
|
+
* @param {{sourceIndex: import("../drivers/base-columns-index.js").default, tableName: string}} args
|
|
213
|
+
* @returns {{columns: string[], name?: string, tableName: string, unique: boolean}}
|
|
214
|
+
*/
|
|
215
|
+
createIndexArgsFromSourceIndex({ sourceIndex, tableName }) {
|
|
216
|
+
/** @type {{columns: string[], name?: string, tableName: string, unique: boolean}} */
|
|
217
|
+
const createIndexArgs = {
|
|
218
|
+
columns: sourceIndex.getColumnNames(),
|
|
219
|
+
tableName,
|
|
220
|
+
unique: sourceIndex.isUnique()
|
|
221
|
+
};
|
|
222
|
+
if (this.targetDb.getType() !== "sqlite") {
|
|
223
|
+
createIndexArgs.name = sourceIndex.getName();
|
|
224
|
+
}
|
|
225
|
+
return createIndexArgs;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Whether two indexes have the same uniqueness and ordered column list.
|
|
229
|
+
* @param {import("../drivers/base-columns-index.js").default} sourceIndex
|
|
230
|
+
* @param {import("../drivers/base-columns-index.js").default} targetIndex
|
|
231
|
+
* @returns {boolean}
|
|
232
|
+
*/
|
|
233
|
+
indexesMatch(sourceIndex, targetIndex) {
|
|
234
|
+
const sourceColumnNames = sourceIndex.getColumnNames();
|
|
235
|
+
const targetColumnNames = targetIndex.getColumnNames();
|
|
236
|
+
if (sourceIndex.isUnique() !== targetIndex.isUnique()) {
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
239
|
+
if (sourceColumnNames.length !== targetColumnNames.length) {
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
for (let columnIndex = 0; columnIndex < sourceColumnNames.length; columnIndex++) {
|
|
243
|
+
if (sourceColumnNames[columnIndex] !== targetColumnNames[columnIndex]) {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return true;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* A stable signature for an index, used to match cloned indexes by shape.
|
|
251
|
+
* @param {import("../drivers/base-columns-index.js").default} index
|
|
252
|
+
* @returns {string}
|
|
253
|
+
*/
|
|
254
|
+
indexSignature(index) {
|
|
255
|
+
return `${index.isUnique() ? "unique" : "index"}:${index.getColumnNames().join(",")}`;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Normalizes a column type to its canonical lowercase form (`int` becomes `integer`).
|
|
259
|
+
* @param {string} columnType
|
|
260
|
+
* @returns {string}
|
|
261
|
+
*/
|
|
262
|
+
normalizedColumnType(columnType) {
|
|
263
|
+
const normalizedType = columnType.toLowerCase();
|
|
264
|
+
if (normalizedType === "int") {
|
|
265
|
+
return "integer";
|
|
266
|
+
}
|
|
267
|
+
return normalizedType;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* The widening rank of a text column type (0 when not a text type).
|
|
271
|
+
* @param {string} columnType
|
|
272
|
+
* @returns {number}
|
|
273
|
+
*/
|
|
274
|
+
textTypeRank(columnType) {
|
|
275
|
+
return TEXT_TYPE_RANKS[this.normalizedColumnType(columnType)] || 0;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Whether the target's text column is narrower than the source's and must be widened.
|
|
279
|
+
* @param {import("../drivers/base-column.js").default} sourceColumn
|
|
280
|
+
* @param {import("../drivers/base-column.js").default} targetColumn
|
|
281
|
+
* @returns {boolean}
|
|
282
|
+
*/
|
|
283
|
+
columnNeedsWidening(sourceColumn, targetColumn) {
|
|
284
|
+
const sourceRank = this.textTypeRank(sourceColumn.getType());
|
|
285
|
+
const targetRank = this.textTypeRank(targetColumn.getType());
|
|
286
|
+
return sourceRank > 0 && targetRank > 0 && sourceRank > targetRank;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Builds TableData column args from a source column, copying type, nullability,
|
|
290
|
+
* length, notes, simple defaults and (for full clones) primary-key flag.
|
|
291
|
+
* @param {import("../drivers/base-column.js").default} sourceColumn
|
|
292
|
+
* @param {{isNewColumn: boolean}} args
|
|
293
|
+
* @returns {Record<string, unknown>}
|
|
294
|
+
*/
|
|
295
|
+
columnArgsFromSourceColumn(sourceColumn, { isNewColumn }) {
|
|
296
|
+
/** @type {{autoIncrement?: boolean, default?: unknown, isNewColumn: boolean, maxLength?: number, notes?: string, null: boolean, primaryKey?: boolean, type: string}} */
|
|
297
|
+
const columnArgs = {
|
|
298
|
+
isNewColumn,
|
|
299
|
+
null: sourceColumn.getNull(),
|
|
300
|
+
type: this.normalizedColumnType(sourceColumn.getType())
|
|
301
|
+
};
|
|
302
|
+
const defaultValue = sourceColumn.getDefault();
|
|
303
|
+
const maxLength = sourceColumn.getMaxLength();
|
|
304
|
+
const notes = sourceColumn.getNotes();
|
|
305
|
+
if (!isNewColumn && sourceColumn.getPrimaryKey()) {
|
|
306
|
+
columnArgs.primaryKey = true;
|
|
307
|
+
}
|
|
308
|
+
if (sourceColumn.getAutoIncrement()) {
|
|
309
|
+
columnArgs.autoIncrement = true;
|
|
310
|
+
}
|
|
311
|
+
// A maxLength of -1 is the MS-SQL "max" sentinel (NVARCHAR(MAX) / VARBINARY(MAX),
|
|
312
|
+
// backing Velocious text/json/blob columns); the column type drives the unbounded
|
|
313
|
+
// SQL, so don't forward -1 as an explicit length (it would emit NVARCHAR(-1)).
|
|
314
|
+
if (maxLength !== undefined && maxLength >= 0) {
|
|
315
|
+
columnArgs.maxLength = maxLength;
|
|
316
|
+
}
|
|
317
|
+
if (notes) {
|
|
318
|
+
columnArgs.notes = notes;
|
|
319
|
+
}
|
|
320
|
+
if (defaultValue !== null && defaultValue !== undefined && SIMPLE_DEFAULT_PATTERN.test(String(defaultValue))) {
|
|
321
|
+
columnArgs.default = defaultValue;
|
|
322
|
+
}
|
|
323
|
+
return columnArgs;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2NoZW1hLWNsb25lci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9kYXRhYmFzZS90ZW5hbnRzL3NjaGVtYS1jbG9uZXIuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsWUFBWTtBQUVaLE9BQU8sZ0JBQWdCLE1BQU0seUJBQXlCLENBQUE7QUFDdEQsT0FBTyxTQUFTLE1BQU0sd0JBQXdCLENBQUE7QUFDOUMsT0FBTyxVQUFVLE1BQU0sOEJBQThCLENBQUE7QUFFckQsTUFBTSxzQkFBc0IsR0FBRyw2Q0FBNkMsQ0FBQTtBQUU1RSxxQ0FBcUM7QUFDckMsTUFBTSxlQUFlLEdBQUc7SUFDdEIsUUFBUSxFQUFFLENBQUM7SUFDWCxJQUFJLEVBQUUsQ0FBQztJQUNQLFVBQVUsRUFBRSxDQUFDO0lBQ2IsUUFBUSxFQUFFLENBQUM7Q0FDWixDQUFBO0FBRUQ7Ozs7Ozs7Ozs7Ozs7R0FhRztBQUNILE1BQU0sQ0FBQyxPQUFPLE9BQU8sWUFBWTtJQUMvQjs7O09BR0c7SUFDSCxZQUFZLEVBQUMsUUFBUSxFQUFFLFFBQVEsRUFBQztRQUM5QixJQUFJLENBQUMsUUFBUSxHQUFHLFFBQVEsQ0FBQTtRQUN4QixJQUFJLENBQUMsUUFBUSxHQUFHLFFBQVEsQ0FBQTtJQUMxQixDQUFDO0lBRUQ7Ozs7O09BS0c7SUFDSCxLQUFLLENBQUMsVUFBVSxDQUFDLFVBQVU7UUFDekIsS0FBSyxNQUFNLFNBQVMsSUFBSSxVQUFVLEVBQUUsQ0FBQztZQUNuQyxNQUFNLElBQUksQ0FBQyxTQUFTLENBQUMsU0FBUyxDQUFDLENBQUE7UUFDakMsQ0FBQztRQUVELE1BQU0sSUFBSSxDQUFDLGVBQWUsRUFBRSxDQUFBO0lBQzlCLENBQUM7SUFFRDs7Ozs7T0FLRztJQUNILEtBQUssQ0FBQyxTQUFTLENBQUMsU0FBUztRQUN2QixNQUFNLFdBQVcsR0FBRyxNQUFNLElBQUksQ0FBQyxRQUFRLENBQUMsY0FBYyxDQUFDLFNBQVMsQ0FBQyxDQUFBO1FB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VUFBVSxDQUFDLGFBQWEsR0FBRyxJQUFJLENBQUE7UUFDakMsQ0FBQztRQUVELGtGQUFrRjtRQUNsRixrRkFBa0Y7UUFDbEYsK0VBQStFO1FBQy9FLElBQUksU0FBUyxLQUFLLFNBQVMsSUFBSSxTQUFTLElBQUksQ0FBQyxFQUFFLENBQUM7WUFDOUMsVUFBVSxDQUFDLFNBQVMsR0FBRyxTQUFTLENBQUE7UUFDbEMsQ0FBQztRQUVELElBQUksS0FBSyxFQUFFLENBQUM7WUFDVixVQUFVLENBQUMsS0FBSyxHQUFHLEtBQUssQ0FBQTtRQUMxQixDQUFDO1FBRUQsSUFBSSxZQUFZLEtBQUssSUFBSSxJQUFJLFlBQVksS0FBSyxTQUFTLElBQUksc0JBQXNCLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxZQUFZLENBQUMsQ0FBQyxFQUFFLENBQUM7WUFDN0csVUFBVSxDQUFDLE9BQU8sR0FBRyxZQUFZLENBQUE7UUFDbkMsQ0FBQztRQUVELE9BQU8sVUFBVSxDQUFBO0lBQ25CLENBQUM7Q0FDRiIsInNvdXJjZXNDb250ZW50IjpbIi8vIEB0cy1jaGVja1xuXG5pbXBvcnQgTWlncmF0aW9uc0xlZGdlciBmcm9tIFwiLi4vbWlncmF0aW9ucy1sZWRnZXIuanNcIlxuaW1wb3J0IFRhYmxlRGF0YSBmcm9tIFwiLi4vdGFibGUtZGF0YS9pbmRleC5qc1wiXG5pbXBvcnQgVGFibGVJbmRleCBmcm9tIFwiLi4vdGFibGUtZGF0YS90YWJsZS1pbmRleC5qc1wiXG5cbmNvbnN0IFNJTVBMRV9ERUZBVUxUX1BBVFRFUk4gPSAvXig/Oi0/XFxkKyg/OlxcLlxcZCspP3xbQS1aYS16MC05IF8uLDovQCstXSopJC9cblxuLyoqIEB0eXBlIHtSZWNvcmQ8c3RyaW5nLCBudW1iZXI+fSAqL1xuY29uc3QgVEVYVF9UWVBFX1JBTktTID0ge1xuICB0aW55dGV4dDogMSxcbiAgdGV4dDogMixcbiAgbWVkaXVtdGV4dDogMyxcbiAgbG9uZ3RleHQ6IDRcbn1cblxuLyoqXG4gKiBDbG9uZXMgdGFibGUgc3RydWN0dXJlIChjb2x1bW5zLCB0ZXh0LXR5cGUgd2lkZW5pbmcsIGluZGV4ZXMpIGZyb20gYSBzb3VyY2UgZGF0YWJhc2VcbiAqIGludG8gYSB0YXJnZXQgZGF0YWJhc2UgZm9yIGEgZ2l2ZW4gc2V0IG9mIHRhYmxlcywgdGhlbiBiYXNlbGluZXMgdGhlIHRhcmdldCdzXG4gKiBgc2NoZW1hX21pZ3JhdGlvbnNgIGxlZGdlciB0byB0aGUgc291cmNlIHZpYSB7QGxpbmsgTWlncmF0aW9uc0xlZGdlcn0uIFRoaXMgaXMgdGhlXG4gKiBtZWNoYW5pc20gbXVsdGktdGVuYW50IGFwcHMgdXNlIHRvIHByb3Zpc2lvbiBhIHRlbmFudCBkYXRhYmFzZSBmcm9tIGEgdGVtcGxhdGUvZ2xvYmFsXG4gKiBkYXRhYmFzZSB3aXRob3V0IHJlLXJ1bm5pbmcgbWlncmF0aW9uczogdGhlIHN0cnVjdHVyZSBpcyBjb3BpZWQgYW5kIHRoZSBsZWRnZXIgaXNcbiAqIHJlY29yZGVkIGFzIGFscmVhZHktYXBwbGllZCwgc28gYSBsYXRlciBgZGI6dGVuYW50czptaWdyYXRlYCBkb2VzIG5vdCByZS1ydW4gYW5cbiAqIGBhZGRDb2x1bW5gIHdob3NlIGNvbHVtbiBhbHJlYWR5IGV4aXN0cy5cbiAqXG4gKiBUaGUgY2xvbmVyIGlzIGludGVudGlvbmFsbHkgcG9saWN5LWZyZWUg4oCUIHRoZSBjYWxsZXIgZGVjaWRlcyB3aGljaCB0YWJsZXMgdG8gc3luYyBhbmRcbiAqIHdoaWNoIGRhdGFiYXNlcyBhcmUgc291cmNlL3RhcmdldC4gSXQgaXMgaWRlbXBvdGVudDogbWlzc2luZyB0YWJsZXMgYXJlIGNyZWF0ZWQsXG4gKiBtaXNzaW5nIGNvbHVtbnMgYWRkZWQsIHRvby1uYXJyb3cgdGV4dCBjb2x1bW5zIHdpZGVuZWQsIGFuZCBtaXNzaW5nIGluZGV4ZXMgY3JlYXRlZDtcbiAqIGFuIGluZGV4IHdob3NlIGRlZmluaXRpb24gZGl2ZXJnZXMgZnJvbSB0aGUgc291cmNlIGlzIHRyZWF0ZWQgYXMgZHJpZnQgYW5kIHRocm93cy5cbiAqL1xuZXhwb3J0IGRlZmF1bHQgY2xhc3MgU2NoZW1hQ2xvbmVyIHtcbiAgLyoqXG4gICAqIENyZWF0ZXMgYSBjbG9uZXIgdGhhdCBjb3BpZXMgdGFibGUgc3RydWN0dXJlIGZyb20gYHNvdXJjZURiYCBpbnRvIGB0YXJnZXREYmAuXG4gICAqIEBwYXJhbSB7e3NvdXJjZURiOiBpbXBvcnQoXCIuLi9kcml2ZXJzL2Jhc2UuanNcIikuZGVmYXVsdCwgdGFyZ2V0RGI6IGltcG9ydChcIi4uL2RyaXZlcnMvYmFzZS5qc1wiKS5kZWZhdWx0fX0gYXJnc1xuICAgKi9cbiAgY29uc3RydWN0b3Ioe3NvdXJjZURiLCB0YXJnZXREYn0pIHtcbiAgICB0aGlzLnNvdXJjZURiID0gc291cmNlRGJcbiAgICB0aGlzLnRhcmdldERiID0gdGFyZ2V0RGJcbiAgfVxuXG4gIC8qKlxuICAgKiBDbG9uZXMgZXZlcnkgZ2l2ZW4gdGFibGUgZnJvbSB0aGUgc291cmNlIGludG8gdGhlIHRhcmdldCwgdGhlbiBiYXNlbGluZXMgdGhlXG4gICAqIHRhcmdldCdzIGxlZGdlciBzbyB0aGUgY2xvbmVkIHNjaGVtYSBpcyByZWNvcmRlZCBhcyBhbHJlYWR5LW1pZ3JhdGVkLlxuICAgKiBAcGFyYW0ge3N0cmluZ1tdfSB0YWJsZU5hbWVzXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPHZvaWQ+fVxuICAgKi9cbiAgYXN5bmMgc3luY1RhYmxlcyh0YWJsZU5hbWVzKSB7XG4gICAgZm9yIChjb25zdCB0YWJsZU5hbWUgb2YgdGFibGVOYW1lcykge1xuICAgICAgYXdhaXQgdGhpcy5zeW5jVGFibGUodGFibGVOYW1lKVxuICAgIH1cblxuICAgIGF3YWl0IHRoaXMucmVjb25jaWxlTGVkZ2VyKClcbiAgfVxuXG4gIC8qKlxuICAgKiBDbG9uZXMgYSBzaW5nbGUgdGFibGUgZnJvbSB0aGUgc291cmNlIGludG8gdGhlIHRhcmdldCwgY3JlYXRpbmcgaXQgb3IgYWRkaW5nIGFuZFxuICAgKiB3aWRlbmluZyBjb2x1bW5zIGFuZCBpbmRleGVzIGFzIG5lZWRlZC5cbiAgICogQHBhcmFtIHtzdHJpbmd9IHRhYmxlTmFtZVxuICAgKiBAcmV0dXJucyB7UHJvbWlzZTx2b2lkPn1cbiAgICovXG4gIGFzeW5jIHN5bmNUYWJsZSh0YWJsZU5hbWUpIHtcbiAgICBjb25zdCBzb3VyY2VUYWJsZSA9IGF3YWl0IHRoaXMuc291cmNlRGIuZ2V0VGFibGVCeU5hbWUodGFibGVOYW1lKVxuXG4gICAgaWYgKCFzb3VyY2VUYWJsZSkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKGBFeHBlY3RlZCBzb3VyY2UgdGFibGUgdG8gZXhpc3Q6ICR7dGFibGVOYW1lfWApXG4gICAgfVxuXG4gICAgaWYgKCFhd2FpdCB0aGlzLnRhcmdldERiLnRhYmxlRXhpc3RzKHRhYmxlTmFtZSkpIHtcbiAgICAgIGF3YWl0IHRoaXMuY3JlYXRlVGFyZ2V0VGFibGUoe3NvdXJjZVRhYmxlLCB0YWJsZU5hbWV9KVxuICAgICAgcmV0dXJuXG4gICAgfVxuXG4gICAgY29uc3QgY2hhbmdlZENvbHVtbnMgPSBhd2FpdCB0aGlzLmVuc3VyZVRhcmdldENvbHVtbnMoe3NvdXJjZVRhYmxlLCB0YWJsZU5hbWV9KVxuXG4gICAgaWYgKGNoYW5nZWRDb2x1bW5zKSB7XG4gICAgICB0aGlzLnRhcmdldERiLmNsZWFyU2NoZW1hQ2FjaGUoKVxuICAgIH1cblxuICAgIGF3YWl0IHRoaXMuZW5zdXJlVGFyZ2V0SW5kZXhlcyh7c291cmNlVGFibGUsIHRhYmxlTmFtZX0pXG4gIH1cblxuICAvKipcbiAgICogQ3JlYXRlcyB0aGUgdGFibGUgaW4gdGhlIHRhcmdldCBmcm9tIHRoZSBzb3VyY2UgdGFibGUncyBjb2x1bW5zIGFuZCBpdHNcbiAgICogbm9uLXByaW1hcnkta2V5IGluZGV4ZXMuXG4gICAqIEBwYXJhbSB7e3NvdXJjZVRhYmxlOiBpbXBvcnQoXCIuLi9kcml2ZXJzL2Jhc2UtdGFibGUuanNcIikuZGVmYXVsdCwgdGFibGVOYW1lOiBzdHJpbmd9fSBhcmdzXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPHZvaWQ+fVxuICAgKi9cbiAgYXN5bmMgY3JlYXRlVGFyZ2V0VGFibGUoe3NvdXJjZVRhYmxlLCB0YWJsZU5hbWV9KSB7XG4gICAgY29uc3QgdGFibGVEYXRhID0gbmV3IFRhYmxlRGF0YSh0YWJsZU5hbWUpXG5cbiAgICBmb3IgKGNvbnN0IHNvdXJjZUNvbHVtbiBvZiBhd2FpdCBzb3VyY2VUYWJsZS5nZXRDb2x1bW5zKCkpIHtcbiAgICAgIHRhYmxlRGF0YS5hZGRDb2x1bW4oc291cmNlQ29sdW1uLmdldE5hbWUoKSwgdGhpcy5jb2x1bW5BcmdzRnJvbVNvdXJjZUNvbHVtbihzb3VyY2VDb2x1bW4sIHtpc05ld0NvbHVtbjogZmFsc2V9KSlcbiAgICB9XG5cbiAgICBmb3IgKGNvbnN0IHNvdXJjZUluZGV4IG9mIGF3YWl0IHNvdXJjZVRhYmxlLmdldEluZGV4ZXMoKSkge1xuICAgICAgaWYgKCFzb3VyY2VJbmRleC5pc1ByaW1hcnlLZXkoKSkge1xuICAgICAgICB0YWJsZURhdGEuYWRkSW5kZXgodGhpcy50YWJsZURhdGFJbmRleEZyb21Tb3VyY2VJbmRleChzb3VyY2VJbmRleCkpXG4gICAgICB9XG4gICAgfVxuXG4gICAgYXdhaXQgdGhpcy50YXJnZXREYi5jcmVhdGVUYWJsZSh0YWJsZURhdGEpXG4gICAgdGhpcy50YXJnZXREYi5jbGVhclNjaGVtYUNhY2hlKClcbiAgfVxuXG4gIC8qKlxuICAgKiBBZGRzIGNvbHVtbnMgcHJlc2VudCBvbiB0aGUgc291cmNlIGJ1dCBtaXNzaW5nIGZyb20gdGhlIHRhcmdldCwgYW5kIHdpZGVuc1xuICAgKiB0b28tbmFycm93IHRhcmdldCB0ZXh0IGNvbHVtbnMuXG4gICAqIEBwYXJhbSB7e3NvdXJjZVRhYmxlOiBpbXBvcnQoXCIuLi9kcml2ZXJzL2Jhc2UtdGFibGUuanNcIikuZGVmYXVsdCwgdGFibGVOYW1lOiBzdHJpbmd9fSBhcmdzXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPGJvb2xlYW4+fSBXaGV0aGVyIGFueSBjb2x1bW4gd2FzIGFkZGVkIG9yIHdpZGVuZWQuXG4gICAqL1xuICBhc3luYyBlbnN1cmVUYXJnZXRDb2x1bW5zKHtzb3VyY2VUYWJsZSwgdGFibGVOYW1lfSkge1xuICAgIGNvbnN0IHNvdXJjZUNvbHVtbnMgPSBhd2FpdCBzb3VyY2VUYWJsZS5nZXRDb2x1bW5zKClcbiAgICBjb25zdCB0YXJnZXRUYWJsZSA9IGF3YWl0IHRoaXMudGFyZ2V0RGIuZ2V0VGFibGVCeU5hbWVPckZhaWwodGFibGVOYW1lKVxuICAgIGNvbnN0IHRhcmdldENvbHVtbnNCeU5hbWUgPSBuZXcgTWFwKClcbiAgICBjb25zdCBtaXNzaW5nQ29sdW1ucyA9IFtdXG4gICAgY29uc3QgY29sdW1uc05lZWRpbmdXaWRlbmluZyA9IFtdXG5cbiAgICBmb3IgKGNvbnN0IHRhcmdldENvbHVtbiBvZiBhd2FpdCB0YXJnZXRUYWJsZS5nZXRDb2x1bW5zKCkpIHtcbiAgICAgIHRhcmdldENvbHVtbnNCeU5hbWUuc2V0KHRhcmdldENvbHVtbi5nZXROYW1lKCksIHRhcmdldENvbHVtbilcbiAgICB9XG5cbiAgICBmb3IgKGNvbnN0IHNvdXJjZUNvbHVtbiBvZiBzb3VyY2VDb2x1bW5zKSB7XG4gICAgICBjb25zdCB0YXJnZXRDb2x1bW4gPSB0YXJnZXRDb2x1bW5zQnlOYW1lLmdldChzb3VyY2VDb2x1bW4uZ2V0TmFtZSgpKVxuXG4gICAgICBpZiAoIXRhcmdldENvbHVtbikge1xuICAgICAgICBtaXNzaW5nQ29sdW1ucy5wdXNoKHNvdXJjZUNvbHVtbilcbiAgICAgIH0gZWxzZSBpZiAodGhpcy5jb2x1bW5OZWVkc1dpZGVuaW5nKHNvdXJjZUNvbHVtbiwgdGFyZ2V0Q29sdW1uKSkge1xuICAgICAgICBjb2x1bW5zTmVlZGluZ1dpZGVuaW5nLnB1c2goc291cmNlQ29sdW1uKVxuICAgICAgfVxuICAgIH1cblxuICAgIGlmIChtaXNzaW5nQ29sdW1ucy5sZW5ndGggPD0gMCAmJiBjb2x1bW5zTmVlZGluZ1dpZGVuaW5nLmxlbmd0aCA8PSAwKSB7XG4gICAgICByZXR1cm4gZmFsc2VcbiAgICB9XG5cbiAgICBjb25zdCB0YWJsZURhdGEgPSBuZXcgVGFibGVEYXRhKHRhYmxlTmFtZSlcblxuICAgIGZvciAoY29uc3Qgc291cmNlQ29sdW1uIG9mIG1pc3NpbmdDb2x1bW5zKSB7XG4gICAgICB0YWJsZURhdGEuYWRkQ29sdW1uKHNvdXJjZUNvbHVtbi5nZXROYW1lKCksIHRoaXMuY29sdW1uQXJnc0Zyb21Tb3VyY2VDb2x1bW4oc291cmNlQ29sdW1uLCB7aXNOZXdDb2x1bW46IHRydWV9KSlcbiAgICB9XG5cbiAgICBmb3IgKGNvbnN0IHNvdXJjZUNvbHVtbiBvZiBjb2x1bW5zTmVlZGluZ1dpZGVuaW5nKSB7XG4gICAgICB0YWJsZURhdGEuYWRkQ29sdW1uKHNvdXJjZUNvbHVtbi5nZXROYW1lKCksIHRoaXMuY29sdW1uQXJnc0Zyb21Tb3VyY2VDb2x1bW4oc291cmNlQ29sdW1uLCB7aXNOZXdDb2x1bW46IGZhbHNlfSkpXG4gICAgfVxuXG4gICAgZm9yIChjb25zdCBhbHRlclNxbCBvZiBhd2FpdCB0aGlzLnRhcmdldERiLmFsdGVyVGFibGVTUUxzKHRhYmxlRGF0YSkpIHtcbiAgICAgIGF3YWl0IHRoaXMudGFyZ2V0RGIucXVlcnkoYWx0ZXJTcWwpXG4gICAgfVxuXG4gICAgcmV0dXJuIHRydWVcbiAgfVxuXG4gIC8qKlxuICAgKiBDcmVhdGVzIG5vbi1wcmltYXJ5LWtleSBpbmRleGVzIHByZXNlbnQgb24gdGhlIHNvdXJjZSBidXQgbWlzc2luZyBmcm9tIHRoZSB0YXJnZXQsXG4gICAqIGFuZCB0aHJvd3MgaWYgYW4gZXhpc3RpbmcgdGFyZ2V0IGluZGV4IGRpdmVyZ2VzIGZyb20gdGhlIHNvdXJjZS5cbiAgICogQHBhcmFtIHt7c291cmNlVGFibGU6IGltcG9ydChcIi4uL2RyaXZlcnMvYmFzZS10YWJsZS5qc1wiKS5kZWZhdWx0LCB0YWJsZU5hbWU6IHN0cmluZ319IGFyZ3NcbiAgICogQHJldHVybnMge1Byb21pc2U8dm9pZD59XG4gICAqL1xuICBhc3luYyBlbnN1cmVUYXJnZXRJbmRleGVzKHtzb3VyY2VUYWJsZSwgdGFibGVOYW1lfSkge1xuICAgIGNvbnN0IHRhcmdldFRhYmxlID0gYXdhaXQgdGhpcy50YXJnZXREYi5nZXRUYWJsZUJ5TmFtZU9yRmFpbCh0YWJsZU5hbWUpXG4gICAgLyoqIEB0eXBlIHtNYXA8c3RyaW5nLCBpbXBvcnQoXCIuLi9kcml2ZXJzL2Jhc2UtY29sdW1ucy1pbmRleC5qc1wiKS5kZWZhdWx0Pn0gKi9cbiAgICBjb25zdCB0YXJnZXRJbmRleGVzQnlOYW1lID0gbmV3IE1hcCgpXG4gICAgY29uc3QgdGFyZ2V0SW5kZXhTaWduYXR1cmVzID0gbmV3IFNldCgpXG4gICAgbGV0IGNyZWF0ZWRJbmRleCA9IGZhbHNlXG5cbiAgICBmb3IgKGNvbnN0IHRhcmdldEluZGV4IG9mIGF3YWl0IHRhcmdldFRhYmxlLmdldEluZGV4ZXMoKSkge1xuICAgICAgdGFyZ2V0SW5kZXhlc0J5TmFtZS5zZXQodGFyZ2V0SW5kZXguZ2V0TmFtZSgpLCB0YXJnZXRJbmRleClcbiAgICAgIHRhcmdldEluZGV4U2lnbmF0dXJlcy5hZGQodGhpcy5pbmRleFNpZ25hdHVyZSh0YXJnZXRJbmRleCkpXG4gICAgfVxuXG4gICAgZm9yIChjb25zdCBzb3VyY2VJbmRleCBvZiBhd2FpdCBzb3VyY2VUYWJsZS5nZXRJbmRleGVzKCkpIHtcbiAgICAgIGlmIChzb3VyY2VJbmRleC5pc1ByaW1hcnlLZXkoKSkge1xuICAgICAgICBjb250aW51ZVxuICAgICAgfVxuXG4gICAgICBjb25zdCBzb3VyY2VJbmRleFNpZ25hdHVyZSA9IHRoaXMuaW5kZXhTaWduYXR1cmUoc291cmNlSW5kZXgpXG5cbiAgICAgIC8vIFNRTGl0ZSBpbmRleCBuYW1lcyBhcmUgdW5pcXVlIHBlci1kYXRhYmFzZSwgbm90IHBlci10YWJsZSwgc28gbWF0Y2ggY2xvbmVkXG4gICAgICAvLyBpbmRleGVzIGJ5IHRoZWlyIGNvbHVtbi91bmlxdWVuZXNzIHNpZ25hdHVyZSByYXRoZXIgdGhhbiB0aGVpciBuYW1lLlxuICAgICAgaWYgKHRoaXMudGFyZ2V0RGIuZ2V0VHlwZSgpID09PSBcInNxbGl0ZVwiICYmIHRhcmdldEluZGV4U2lnbmF0dXJlcy5oYXMoc291cmNlSW5kZXhTaWduYXR1cmUpKSB7XG4gICAgICAgIGNvbnRpbnVlXG4gICAgICB9XG5cbiAgICAgIGNvbnN0IHRhcmdldEluZGV4ID0gdGhpcy50YXJnZXREYi5nZXRUeXBlKCkgPT09IFwic3FsaXRlXCIgPyB1bmRlZmluZWQgOiB0YXJnZXRJbmRleGVzQnlOYW1lLmdldChzb3VyY2VJbmRleC5nZXROYW1lKCkpXG5cbiAgICAgIGlmICghdGFyZ2V0SW5kZXgpIHtcbiAgICAgICAgY29uc3QgY3JlYXRlSW5kZXhTcWxzID0gYXdhaXQgdGhpcy50YXJnZXREYi5jcmVhdGVJbmRleFNRTHModGhpcy5jcmVhdGVJbmRleEFyZ3NGcm9tU291cmNlSW5kZXgoe3NvdXJjZUluZGV4LCB0YWJsZU5hbWV9KSlcblxuICAgICAgICBmb3IgKGNvbnN0IGNyZWF0ZUluZGV4U3FsIG9mIGNyZWF0ZUluZGV4U3Fscykge1xuICAgICAgICAgIGF3YWl0IHRoaXMudGFyZ2V0RGIucXVlcnkoY3JlYXRlSW5kZXhTcWwpXG4gICAgICAgIH1cblxuICAgICAgICBjcmVhdGVkSW5kZXggPSB0cnVlXG4gICAgICAgIHRhcmdldEluZGV4U2lnbmF0dXJlcy5hZGQoc291cmNlSW5kZXhTaWduYXR1cmUpXG4gICAgICAgIGNvbnRpbnVlXG4gICAgICB9XG5cbiAgICAgIGlmICghdGhpcy5pbmRleGVzTWF0Y2goc291cmNlSW5kZXgsIHRhcmdldEluZGV4KSkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYFNjaGVtYSBjbG9uZSBpbmRleCBkcmlmdCBmb3IgJHt0YWJsZU5hbWV9LiR7c291cmNlSW5kZXguZ2V0TmFtZSgpfWApXG4gICAgICB9XG4gICAgfVxuXG4gICAgaWYgKGNyZWF0ZWRJbmRleCkge1xuICAgICAgdGhpcy50YXJnZXREYi5jbGVhclNjaGVtYUNhY2hlKClcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogQmFzZWxpbmVzIHRoZSB0YXJnZXQgbGVkZ2VyIHNvIHRoZSBjbG9uZWQgc2NoZW1hIGlzIHJlY29yZGVkIGFzIGFscmVhZHktYXBwbGllZC5cbiAgICogQHJldHVybnMge1Byb21pc2U8c3RyaW5nW10+fSBUaGUgdmVyc2lvbnMgbmV3bHkgcmVjb3JkZWQgb24gdGhlIHRhcmdldC5cbiAgICovXG4gIGFzeW5jIHJlY29uY2lsZUxlZGdlcigpIHtcbiAgICByZXR1cm4gYXdhaXQgTWlncmF0aW9uc0xlZGdlci5iYXNlbGluZUZyb21EYXRhYmFzZSh7c291cmNlRGI6IHRoaXMuc291cmNlRGIsIHRhcmdldERiOiB0aGlzLnRhcmdldERifSlcbiAgfVxuXG4gIC8qKlxuICAgKiBXaGV0aGVyIHRoZSB0YXJnZXQgbGVkZ2VyIGlzIG1pc3NpbmcgYW55IHZlcnNpb24gYXBwbGllZCBvbiB0aGUgc291cmNlIOKAlCBpLmUuIHRoZVxuICAgKiB0YXJnZXQgc2NoZW1hIG1heSBoYXZlIGJlZW4gYWR2YW5jZWQgb3V0IG9mIGJhbmQgd2l0aG91dCByZWNvcmRpbmcgaXQuXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPGJvb2xlYW4+fVxuICAgKi9cbiAgYXN5bmMgbGVkZ2VyRHJpZnRzRnJvbVNvdXJjZSgpIHtcbiAgICBpZiAoIWF3YWl0IE1pZ3JhdGlvbnNMZWRnZXIudGFibGVFeGlzdHModGhpcy50YXJnZXREYikpIHtcbiAgICAgIHJldHVybiB0cnVlXG4gICAgfVxuXG4gICAgY29uc3Qgc291cmNlVmVyc2lvbnMgPSBhd2FpdCBNaWdyYXRpb25zTGVkZ2VyLmFwcGxpZWRWZXJzaW9ucyh0aGlzLnNvdXJjZURiKVxuICAgIGNvbnN0IHRhcmdldFZlcnNpb25TZXQgPSBuZXcgU2V0KGF3YWl0IE1pZ3JhdGlvbnNMZWRnZXIuYXBwbGllZFZlcnNpb25zKHRoaXMudGFyZ2V0RGIpKVxuXG4gICAgcmV0dXJuIHNvdXJjZVZlcnNpb25zLnNvbWUoKHZlcnNpb24pID0+ICF0YXJnZXRWZXJzaW9uU2V0Lmhhcyh2ZXJzaW9uKSlcbiAgfVxuXG4gIC8qKlxuICAgKiBNYXBzIGEgc291cmNlIGluZGV4IGludG8gYSBUYWJsZURhdGEgaW5kZXggZm9yIHRhYmxlIGNyZWF0aW9uIChTUUxpdGUgb21pdHMgdGhlXG4gICAqIGluZGV4IG5hbWUgc28gdGhlIGRyaXZlciBjYW4gZ2VuZXJhdGUgYSB1bmlxdWUgb25lKS5cbiAgICogQHBhcmFtIHtpbXBvcnQoXCIuLi9kcml2ZXJzL2Jhc2UtY29sdW1ucy1pbmRleC5qc1wiKS5kZWZhdWx0fSBzb3VyY2VJbmRleFxuICAgKiBAcmV0dXJucyB7VGFibGVJbmRleH1cbiAgICovXG4gIHRhYmxlRGF0YUluZGV4RnJvbVNvdXJjZUluZGV4KHNvdXJjZUluZGV4KSB7XG4gICAgLyoqIEB0eXBlIHt7bmFtZT86IHN0cmluZywgdW5pcXVlOiBib29sZWFufX0gKi9cbiAgICBjb25zdCBhcmdzID0ge3VuaXF1ZTogc291cmNlSW5kZXguaXNVbmlxdWUoKX1cblxuICAgIC8vIFNRTGl0ZSBpbmRleCBuYW1lcyBhcmUgdW5pcXVlIHBlci1kYXRhYmFzZSwgbm90IHBlci10YWJsZSwgc28gbGV0IHRoZSBkcml2ZXJcbiAgICAvLyBnZW5lcmF0ZSBvbmU7IG90aGVyIGRyaXZlcnMgcHJlc2VydmUgdGhlIHNvdXJjZSBpbmRleCBuYW1lLiBCdWlsZCB0aGUgVGFibGVJbmRleFxuICAgIC8vIGRpcmVjdGx5IChyYXRoZXIgdGhhbiB2aWEgdGhlIGRyaXZlcidzIGdldFRhYmxlRGF0YUluZGV4LCB3aGljaCBvbmx5IE15U1FMIGFuZFxuICAgIC8vIFNRTGl0ZSBpbXBsZW1lbnQpIHNvIGNsb25pbmcgYSBQb3N0Z3JlU1FMIG9yIE1TLVNRTCBzb3VyY2UgdGFibGUgd29ya3MgdG9vLlxuICAgIGlmICh0aGlzLnRhcmdldERiLmdldFR5cGUoKSAhPT0gXCJzcWxpdGVcIikge1xuICAgICAgYXJncy5uYW1lID0gc291cmNlSW5kZXguZ2V0TmFtZSgpXG4gICAgfVxuXG4gICAgcmV0dXJuIG5ldyBUYWJsZUluZGV4KHNvdXJjZUluZGV4LmdldENvbHVtbk5hbWVzKCksIGFyZ3MpXG4gIH1cblxuICAvKipcbiAgICogQnVpbGRzIGRyaXZlciBjcmVhdGUtaW5kZXggYXJncyBmcm9tIGEgc291cmNlIGluZGV4ICh0aGUgaW5kZXggbmFtZSBpcyBvbWl0dGVkIG9uXG4gICAqIFNRTGl0ZSwgd2hlcmUgaW5kZXggbmFtZXMgYXJlIHVuaXF1ZSBwZXItZGF0YWJhc2UgcmF0aGVyIHRoYW4gcGVyLXRhYmxlKS5cbiAgICogQHBhcmFtIHt7c291cmNlSW5kZXg6IGltcG9ydChcIi4uL2RyaXZlcnMvYmFzZS1jb2x1bW5zLWluZGV4LmpzXCIpLmRlZmF1bHQsIHRhYmxlTmFtZTogc3RyaW5nfX0gYXJnc1xuICAgKiBAcmV0dXJucyB7e2NvbHVtbnM6IHN0cmluZ1tdLCBuYW1lPzogc3RyaW5nLCB0YWJsZU5hbWU6IHN0cmluZywgdW5pcXVlOiBib29sZWFufX1cbiAgICovXG4gIGNyZWF0ZUluZGV4QXJnc0Zyb21Tb3VyY2VJbmRleCh7c291cmNlSW5kZXgsIHRhYmxlTmFtZX0pIHtcbiAgICAvKiogQHR5cGUge3tjb2x1bW5zOiBzdHJpbmdbXSwgbmFtZT86IHN0cmluZywgdGFibGVOYW1lOiBzdHJpbmcsIHVuaXF1ZTogYm9vbGVhbn19ICovXG4gICAgY29uc3QgY3JlYXRlSW5kZXhBcmdzID0ge1xuICAgICAgY29sdW1uczogc291cmNlSW5kZXguZ2V0Q29sdW1uTmFtZXMoKSxcbiAgICAgIHRhYmxlTmFtZSxcbiAgICAgIHVuaXF1ZTogc291cmNlSW5kZXguaXNVbmlxdWUoKVxuICAgIH1cblxuICAgIGlmICh0aGlzLnRhcmdldERiLmdldFR5cGUoKSAhPT0gXCJzcWxpdGVcIikge1xuICAgICAgY3JlYXRlSW5kZXhBcmdzLm5hbWUgPSBzb3VyY2VJbmRleC5nZXROYW1lKClcbiAgICB9XG5cbiAgICByZXR1cm4gY3JlYXRlSW5kZXhBcmdzXG4gIH1cblxuICAvKipcbiAgICogV2hldGhlciB0d28gaW5kZXhlcyBoYXZlIHRoZSBzYW1lIHVuaXF1ZW5lc3MgYW5kIG9yZGVyZWQgY29sdW1uIGxpc3QuXG4gICAqIEBwYXJhbSB7aW1wb3J0KFwiLi4vZHJpdmVycy9iYXNlLWNvbHVtbnMtaW5kZXguanNcIikuZGVmYXVsdH0gc291cmNlSW5kZXhcbiAgICogQHBhcmFtIHtpbXBvcnQoXCIuLi9kcml2ZXJzL2Jhc2UtY29sdW1ucy1pbmRleC5qc1wiKS5kZWZhdWx0fSB0YXJnZXRJbmRleFxuICAgKiBAcmV0dXJucyB7Ym9vbGVhbn1cbiAgICovXG4gIGluZGV4ZXNNYXRjaChzb3VyY2VJbmRleCwgdGFyZ2V0SW5kZXgpIHtcbiAgICBjb25zdCBzb3VyY2VDb2x1bW5OYW1lcyA9IHNvdXJjZUluZGV4LmdldENvbHVtbk5hbWVzKClcbiAgICBjb25zdCB0YXJnZXRDb2x1bW5OYW1lcyA9IHRhcmdldEluZGV4LmdldENvbHVtbk5hbWVzKClcblxuICAgIGlmIChzb3VyY2VJbmRleC5pc1VuaXF1ZSgpICE9PSB0YXJnZXRJbmRleC5pc1VuaXF1ZSgpKSB7XG4gICAgICByZXR1cm4gZmFsc2VcbiAgICB9XG5cbiAgICBpZiAoc291cmNlQ29sdW1uTmFtZXMubGVuZ3RoICE9PSB0YXJnZXRDb2x1bW5OYW1lcy5sZW5ndGgpIHtcbiAgICAgIHJldHVybiBmYWxzZVxuICAgIH1cblxuICAgIGZvciAobGV0IGNvbHVtbkluZGV4ID0gMDsgY29sdW1uSW5kZXggPCBzb3VyY2VDb2x1bW5OYW1lcy5sZW5ndGg7IGNvbHVtbkluZGV4KyspIHtcbiAgICAgIGlmIChzb3VyY2VDb2x1bW5OYW1lc1tjb2x1bW5JbmRleF0gIT09IHRhcmdldENvbHVtbk5hbWVzW2NvbHVtbkluZGV4XSkge1xuICAgICAgICByZXR1cm4gZmFsc2VcbiAgICAgIH1cbiAgICB9XG5cbiAgICByZXR1cm4gdHJ1ZVxuICB9XG5cbiAgLyoqXG4gICAqIEEgc3RhYmxlIHNpZ25hdHVyZSBmb3IgYW4gaW5kZXgsIHVzZWQgdG8gbWF0Y2ggY2xvbmVkIGluZGV4ZXMgYnkgc2hhcGUuXG4gICAqIEBwYXJhbSB7aW1wb3J0KFwiLi4vZHJpdmVycy9iYXNlLWNvbHVtbnMtaW5kZXguanNcIikuZGVmYXVsdH0gaW5kZXhcbiAgICogQHJldHVybnMge3N0cmluZ31cbiAgICovXG4gIGluZGV4U2lnbmF0dXJlKGluZGV4KSB7XG4gICAgcmV0dXJuIGAke2luZGV4LmlzVW5pcXVlKCkgPyBcInVuaXF1ZVwiIDogXCJpbmRleFwifToke2luZGV4LmdldENvbHVtbk5hbWVzKCkuam9pbihcIixcIil9YFxuICB9XG5cbiAgLyoqXG4gICAqIE5vcm1hbGl6ZXMgYSBjb2x1bW4gdHlwZSB0byBpdHMgY2Fub25pY2FsIGxvd2VyY2FzZSBmb3JtIChgaW50YCBiZWNvbWVzIGBpbnRlZ2VyYCkuXG4gICAqIEBwYXJhbSB7c3RyaW5nfSBjb2x1bW5UeXBlXG4gICAqIEByZXR1cm5zIHtzdHJpbmd9XG4gICAqL1xuICBub3JtYWxpemVkQ29sdW1uVHlwZShjb2x1bW5UeXBlKSB7XG4gICAgY29uc3Qgbm9ybWFsaXplZFR5cGUgPSBjb2x1bW5UeXBlLnRvTG93ZXJDYXNlKClcblxuICAgIGlmIChub3JtYWxpemVkVHlwZSA9PT0gXCJpbnRcIikge1xuICAgICAgcmV0dXJuIFwiaW50ZWdlclwiXG4gICAgfVxuXG4gICAgcmV0dXJuIG5vcm1hbGl6ZWRUeXBlXG4gIH1cblxuICAvKipcbiAgICogVGhlIHdpZGVuaW5nIHJhbmsgb2YgYSB0ZXh0IGNvbHVtbiB0eXBlICgwIHdoZW4gbm90IGEgdGV4dCB0eXBlKS5cbiAgICogQHBhcmFtIHtzdHJpbmd9IGNvbHVtblR5cGVcbiAgICogQHJldHVybnMge251bWJlcn1cbiAgICovXG4gIHRleHRUeXBlUmFuayhjb2x1bW5UeXBlKSB7XG4gICAgcmV0dXJuIFRFWFRfVFlQRV9SQU5LU1t0aGlzLm5vcm1hbGl6ZWRDb2x1bW5UeXBlKGNvbHVtblR5cGUpXSB8fCAwXG4gIH1cblxuICAvKipcbiAgICogV2hldGhlciB0aGUgdGFyZ2V0J3MgdGV4dCBjb2x1bW4gaXMgbmFycm93ZXIgdGhhbiB0aGUgc291cmNlJ3MgYW5kIG11c3QgYmUgd2lkZW5lZC5cbiAgICogQHBhcmFtIHtpbXBvcnQoXCIuLi9kcml2ZXJzL2Jhc2UtY29sdW1uLmpzXCIpLmRlZmF1bHR9IHNvdXJjZUNvbHVtblxuICAgKiBAcGFyYW0ge2ltcG9ydChcIi4uL2RyaXZlcnMvYmFzZS1jb2x1bW4uanNcIikuZGVmYXVsdH0gdGFyZ2V0Q29sdW1uXG4gICAqIEByZXR1cm5zIHtib29sZWFufVxuICAgKi9cbiAgY29sdW1uTmVlZHNXaWRlbmluZyhzb3VyY2VDb2x1bW4sIHRhcmdldENvbHVtbikge1xuICAgIGNvbnN0IHNvdXJjZVJhbmsgPSB0aGlzLnRleHRUeXBlUmFuayhzb3VyY2VDb2x1bW4uZ2V0VHlwZSgpKVxuICAgIGNvbnN0IHRhcmdldFJhbmsgPSB0aGlzLnRleHRUeXBlUmFuayh0YXJnZXRDb2x1bW4uZ2V0VHlwZSgpKVxuXG4gICAgcmV0dXJuIHNvdXJjZVJhbmsgPiAwICYmIHRhcmdldFJhbmsgPiAwICYmIHNvdXJjZVJhbmsgPiB0YXJnZXRSYW5rXG4gIH1cblxuICAvKipcbiAgICogQnVpbGRzIFRhYmxlRGF0YSBjb2x1bW4gYXJncyBmcm9tIGEgc291cmNlIGNvbHVtbiwgY29weWluZyB0eXBlLCBudWxsYWJpbGl0eSxcbiAgICogbGVuZ3RoLCBub3Rlcywgc2ltcGxlIGRlZmF1bHRzIGFuZCAoZm9yIGZ1bGwgY2xvbmVzKSBwcmltYXJ5LWtleSBmbGFnLlxuICAgKiBAcGFyYW0ge2ltcG9ydChcIi4uL2RyaXZlcnMvYmFzZS1jb2x1bW4uanNcIikuZGVmYXVsdH0gc291cmNlQ29sdW1uXG4gICAqIEBwYXJhbSB7e2lzTmV3Q29sdW1uOiBib29sZWFufX0gYXJnc1xuICAgKiBAcmV0dXJucyB7UmVjb3JkPHN0cmluZywgdW5rbm93bj59XG4gICAqL1xuICBjb2x1bW5BcmdzRnJvbVNvdXJjZUNvbHVtbihzb3VyY2VDb2x1bW4sIHtpc05ld0NvbHVtbn0pIHtcbiAgICAvKiogQHR5cGUge3thdXRvSW5jcmVtZW50PzogYm9vbGVhbiwgZGVmYXVsdD86IHVua25vd24sIGlzTmV3Q29sdW1uOiBib29sZWFuLCBtYXhMZW5ndGg/OiBudW1iZXIsIG5vdGVzPzogc3RyaW5nLCBudWxsOiBib29sZWFuLCBwcmltYXJ5S2V5PzogYm9vbGVhbiwgdHlwZTogc3RyaW5nfX0gKi9cbiAgICBjb25zdCBjb2x1bW5BcmdzID0ge1xuICAgICAgaXNOZXdDb2x1bW4sXG4gICAgICBudWxsOiBzb3VyY2VDb2x1bW4uZ2V0TnVsbCgpLFxuICAgICAgdHlwZTogdGhpcy5ub3JtYWxpemVkQ29sdW1uVHlwZShzb3VyY2VDb2x1bW4uZ2V0VHlwZSgpKVxuICAgIH1cbiAgICBjb25zdCBkZWZhdWx0VmFsdWUgPSBzb3VyY2VDb2x1bW4uZ2V0RGVmYXVsdCgpXG4gICAgY29uc3QgbWF4TGVuZ3RoID0gc291cmNlQ29sdW1uLmdldE1heExlbmd0aCgpXG4gICAgY29uc3Qgbm90ZXMgPSBzb3VyY2VDb2x1bW4uZ2V0Tm90ZXMoKVxuXG4gICAgaWYgKCFpc05ld0NvbHVtbiAmJiBzb3VyY2VDb2x1bW4uZ2V0UHJpbWFyeUtleSgpKSB7XG4gICAgICBjb2x1bW5BcmdzLnByaW1hcnlLZXkgPSB0cnVlXG4gICAgfVxuXG4gICAgaWYgKHNvdXJjZUNvbHVtbi5nZXRBdXRvSW5jcmVtZW50KCkpIHtcbiAgICAgIGNvbHVtbkFyZ3MuYXV0b0luY3JlbWVudCA9IHRydWVcbiAgICB9XG5cbiAgICAvLyBBIG1heExlbmd0aCBvZiAtMSBpcyB0aGUgTVMtU1FMIFwibWF4XCIgc2VudGluZWwgKE5WQVJDSEFSKE1BWCkgLyBWQVJCSU5BUlkoTUFYKSxcbiAgICAvLyBiYWNraW5nIFZlbG9jaW91cyB0ZXh0L2pzb24vYmxvYiBjb2x1bW5zKTsgdGhlIGNvbHVtbiB0eXBlIGRyaXZlcyB0aGUgdW5ib3VuZGVkXG4gICAgLy8gU1FMLCBzbyBkb24ndCBmb3J3YXJkIC0xIGFzIGFuIGV4cGxpY2l0IGxlbmd0aCAoaXQgd291bGQgZW1pdCBOVkFSQ0hBUigtMSkpLlxuICAgIGlmIChtYXhMZW5ndGggIT09IHVuZGVmaW5lZCAmJiBtYXhMZW5ndGggPj0gMCkge1xuICAgICAgY29sdW1uQXJncy5tYXhMZW5ndGggPSBtYXhMZW5ndGhcbiAgICB9XG5cbiAgICBpZiAobm90ZXMpIHtcbiAgICAgIGNvbHVtbkFyZ3Mubm90ZXMgPSBub3Rlc1xuICAgIH1cblxuICAgIGlmIChkZWZhdWx0VmFsdWUgIT09IG51bGwgJiYgZGVmYXVsdFZhbHVlICE9PSB1bmRlZmluZWQgJiYgU0lNUExFX0RFRkFVTFRfUEFUVEVSTi50ZXN0KFN0cmluZyhkZWZhdWx0VmFsdWUpKSkge1xuICAgICAgY29sdW1uQXJncy5kZWZhdWx0ID0gZGVmYXVsdFZhbHVlXG4gICAgfVxuXG4gICAgcmV0dXJuIGNvbHVtbkFyZ3NcbiAgfVxufVxuIl19
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Describes how one table's rows are partitioned between tenants so a DataCopier can select
|
|
3
|
+
* the subset that belongs to a given tenant key.
|
|
4
|
+
*
|
|
5
|
+
* A row belongs to the tenant when its `keyColumn` equals the tenant key value, or — for
|
|
6
|
+
* tables that have no direct tenant column — when its `parentColumn` references a row of
|
|
7
|
+
* `parentTableName` that was itself already selected as belonging to the tenant. Exactly
|
|
8
|
+
* one of `keyColumn` or the (`parentTableName` + `parentColumn`) pair must be set; a
|
|
9
|
+
* parent-scoped entry must appear after its parent in the plan so the parent ids are known
|
|
10
|
+
* by the time the child is loaded.
|
|
11
|
+
*/
|
|
12
|
+
export type TenantTablePlanEntry = {
|
|
13
|
+
/**
|
|
14
|
+
* Name of the table whose rows are partitioned by tenant.
|
|
15
|
+
*/
|
|
16
|
+
tableName: string;
|
|
17
|
+
/**
|
|
18
|
+
* Column matched directly against the tenant key value.
|
|
19
|
+
*/
|
|
20
|
+
keyColumn?: string | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* Earlier-in-plan table whose selected rows scope this one.
|
|
23
|
+
*/
|
|
24
|
+
parentTableName?: string | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Column on this table referencing the parent table's primary key.
|
|
27
|
+
*/
|
|
28
|
+
parentColumn?: string | undefined;
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=tenant-table-plan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tenant-table-plan.d.ts","sourceRoot":"","sources":["../../../../src/database/tenants/tenant-table-plan.js"],"names":[],"mappings":";;;;;;;;;;;;;;;eAac,MAAM"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
export {};
|
|
3
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGVuYW50LXRhYmxlLXBsYW4uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvZGF0YWJhc2UvdGVuYW50cy90ZW5hbnQtdGFibGUtcGxhbi5qcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxZQUFZIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQHRzLWNoZWNrXG5cbi8qKlxuICogRGVzY3JpYmVzIGhvdyBvbmUgdGFibGUncyByb3dzIGFyZSBwYXJ0aXRpb25lZCBiZXR3ZWVuIHRlbmFudHMgc28gYSBEYXRhQ29waWVyIGNhbiBzZWxlY3RcbiAqIHRoZSBzdWJzZXQgdGhhdCBiZWxvbmdzIHRvIGEgZ2l2ZW4gdGVuYW50IGtleS5cbiAqXG4gKiBBIHJvdyBiZWxvbmdzIHRvIHRoZSB0ZW5hbnQgd2hlbiBpdHMgYGtleUNvbHVtbmAgZXF1YWxzIHRoZSB0ZW5hbnQga2V5IHZhbHVlLCBvciDigJQgZm9yXG4gKiB0YWJsZXMgdGhhdCBoYXZlIG5vIGRpcmVjdCB0ZW5hbnQgY29sdW1uIOKAlCB3aGVuIGl0cyBgcGFyZW50Q29sdW1uYCByZWZlcmVuY2VzIGEgcm93IG9mXG4gKiBgcGFyZW50VGFibGVOYW1lYCB0aGF0IHdhcyBpdHNlbGYgYWxyZWFkeSBzZWxlY3RlZCBhcyBiZWxvbmdpbmcgdG8gdGhlIHRlbmFudC4gRXhhY3RseVxuICogb25lIG9mIGBrZXlDb2x1bW5gIG9yIHRoZSAoYHBhcmVudFRhYmxlTmFtZWAgKyBgcGFyZW50Q29sdW1uYCkgcGFpciBtdXN0IGJlIHNldDsgYVxuICogcGFyZW50LXNjb3BlZCBlbnRyeSBtdXN0IGFwcGVhciBhZnRlciBpdHMgcGFyZW50IGluIHRoZSBwbGFuIHNvIHRoZSBwYXJlbnQgaWRzIGFyZSBrbm93blxuICogYnkgdGhlIHRpbWUgdGhlIGNoaWxkIGlzIGxvYWRlZC5cbiAqIEB0eXBlZGVmIHtvYmplY3R9IFRlbmFudFRhYmxlUGxhbkVudHJ5XG4gKiBAcHJvcGVydHkge3N0cmluZ30gdGFibGVOYW1lIE5hbWUgb2YgdGhlIHRhYmxlIHdob3NlIHJvd3MgYXJlIHBhcnRpdGlvbmVkIGJ5IHRlbmFudC5cbiAqIEBwcm9wZXJ0eSB7c3RyaW5nfSBba2V5Q29sdW1uXSBDb2x1bW4gbWF0Y2hlZCBkaXJlY3RseSBhZ2FpbnN0IHRoZSB0ZW5hbnQga2V5IHZhbHVlLlxuICogQHByb3BlcnR5IHtzdHJpbmd9IFtwYXJlbnRUYWJsZU5hbWVdIEVhcmxpZXItaW4tcGxhbiB0YWJsZSB3aG9zZSBzZWxlY3RlZCByb3dzIHNjb3BlIHRoaXMgb25lLlxuICogQHByb3BlcnR5IHtzdHJpbmd9IFtwYXJlbnRDb2x1bW5dIENvbHVtbiBvbiB0aGlzIHRhYmxlIHJlZmVyZW5jaW5nIHRoZSBwYXJlbnQgdGFibGUncyBwcmltYXJ5IGtleS5cbiAqL1xuXG5leHBvcnQge31cbiJdfQ==
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runs a callback once within each tenant's context for a tenant database identifier,
|
|
3
|
+
* optionally several tenants at a time. Each tenant is entered with `runWithTenant`, and a
|
|
4
|
+
* tenant whose database identifier is inactive throws rather than running the callback
|
|
5
|
+
* against the wrong connection. When iterating in parallel the per-tenant failures are
|
|
6
|
+
* collected and rethrown together as an `AggregateError` so one bad tenant does not hide the
|
|
7
|
+
* others. This is the iteration engine shared by the `db:tenants:*` CLI commands and the
|
|
8
|
+
* runtime {@link Tenant} façade; the caller is responsible for producing the tenant list.
|
|
9
|
+
*/
|
|
10
|
+
export default class TenantIterator {
|
|
11
|
+
/**
|
|
12
|
+
* Builds a human-readable label for a tenant for use in error messages.
|
|
13
|
+
* @param {?} tenant
|
|
14
|
+
* @returns {string}
|
|
15
|
+
*/
|
|
16
|
+
static tenantLabel(tenant: unknown): string;
|
|
17
|
+
/**
|
|
18
|
+
* Creates an iterator bound to a configuration and tenant database identifier.
|
|
19
|
+
* @param {{configuration: import("../configuration.js").default, identifier: string, parallelCount?: number}} args
|
|
20
|
+
*/
|
|
21
|
+
constructor({ configuration, identifier, parallelCount }: {
|
|
22
|
+
configuration: import("../configuration.js").default;
|
|
23
|
+
identifier: string;
|
|
24
|
+
parallelCount?: number;
|
|
25
|
+
});
|
|
26
|
+
configuration: import("../configuration.js").default;
|
|
27
|
+
identifier: string;
|
|
28
|
+
parallelCount: number;
|
|
29
|
+
/**
|
|
30
|
+
* Runs `callback` within each tenant's context and returns how many tenants were processed.
|
|
31
|
+
* @param {Array<?>} tenants
|
|
32
|
+
* @param {function({databaseConfiguration: import("../configuration-types.js").DatabaseConfigurationType, tenant: ?}) : Promise<void>} callback
|
|
33
|
+
* @returns {Promise<number>}
|
|
34
|
+
*/
|
|
35
|
+
run(tenants: Array<unknown>, callback: (arg0: {
|
|
36
|
+
databaseConfiguration: import("../configuration-types.js").DatabaseConfigurationType;
|
|
37
|
+
tenant: unknown;
|
|
38
|
+
}) => Promise<void>): Promise<number>;
|
|
39
|
+
/**
|
|
40
|
+
* Enters one tenant's context and runs the callback, asserting the database is active first.
|
|
41
|
+
* @param {{callback: function({databaseConfiguration: import("../configuration-types.js").DatabaseConfigurationType, tenant: ?}) : Promise<void>, tenant: ?}} args
|
|
42
|
+
* @returns {Promise<void>}
|
|
43
|
+
*/
|
|
44
|
+
runTenantCallback({ callback, tenant }: {
|
|
45
|
+
callback: (arg0: {
|
|
46
|
+
databaseConfiguration: import("../configuration-types.js").DatabaseConfigurationType;
|
|
47
|
+
tenant: unknown;
|
|
48
|
+
}) => Promise<void>;
|
|
49
|
+
tenant: unknown;
|
|
50
|
+
}): Promise<void>;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=tenant-iterator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tenant-iterator.d.ts","sourceRoot":"","sources":["../../../src/tenants/tenant-iterator.js"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH;IAmFE;;;;OAIG;IACH,2BAHW,OAAC,GACC,MAAM,CAYlB;IAjGD;;;OAGG;IACH,0DAFW;QAAC,aAAa,EAAE,OAAO,qBAAqB,EAAE,OAAO,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAA;KAAC,EAM5G;IAHC,qDAAkC;IAClC,mBAA4B;IAC5B,sBAAkC;IAGpC;;;;;OAKG;IACH,aAJW,KAAK,CAAC,OAAC,CAAC,YACR,CAAS,IAAiG,EAAjG;QAAC,qBAAqB,EAAE,OAAO,2BAA2B,EAAE,yBAAyB,CAAC;QAAC,MAAM,EAAE,OAAC,CAAA;KAAC,KAAI,OAAO,CAAC,IAAI,CAAC,GACzH,OAAO,CAAC,MAAM,CAAC,CAgD3B;IAED;;;;OAIG;IACH,wCAHW;QAAC,QAAQ,EAAE,CAAS,IAAiG,EAAjG;YAAC,qBAAqB,EAAE,OAAO,2BAA2B,EAAE,yBAAyB,CAAC;YAAC,MAAM,EAAE,OAAC,CAAA;SAAC,KAAI,OAAO,CAAC,IAAI,CAAC,CAAC;QAAC,MAAM,EAAE,OAAC,CAAA;KAAC,GAChJ,OAAO,CAAC,IAAI,CAAC,CAazB;CAkBF"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/**
|
|
3
|
+
* Runs a callback once within each tenant's context for a tenant database identifier,
|
|
4
|
+
* optionally several tenants at a time. Each tenant is entered with `runWithTenant`, and a
|
|
5
|
+
* tenant whose database identifier is inactive throws rather than running the callback
|
|
6
|
+
* against the wrong connection. When iterating in parallel the per-tenant failures are
|
|
7
|
+
* collected and rethrown together as an `AggregateError` so one bad tenant does not hide the
|
|
8
|
+
* others. This is the iteration engine shared by the `db:tenants:*` CLI commands and the
|
|
9
|
+
* runtime {@link Tenant} façade; the caller is responsible for producing the tenant list.
|
|
10
|
+
*/
|
|
11
|
+
export default class TenantIterator {
|
|
12
|
+
/**
|
|
13
|
+
* Creates an iterator bound to a configuration and tenant database identifier.
|
|
14
|
+
* @param {{configuration: import("../configuration.js").default, identifier: string, parallelCount?: number}} args
|
|
15
|
+
*/
|
|
16
|
+
constructor({ configuration, identifier, parallelCount = 1 }) {
|
|
17
|
+
this.configuration = configuration;
|
|
18
|
+
this.identifier = identifier;
|
|
19
|
+
this.parallelCount = parallelCount;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Runs `callback` within each tenant's context and returns how many tenants were processed.
|
|
23
|
+
* @param {Array<?>} tenants
|
|
24
|
+
* @param {function({databaseConfiguration: import("../configuration-types.js").DatabaseConfigurationType, tenant: ?}) : Promise<void>} callback
|
|
25
|
+
* @returns {Promise<number>}
|
|
26
|
+
*/
|
|
27
|
+
async run(tenants, callback) {
|
|
28
|
+
if (this.parallelCount <= 1) {
|
|
29
|
+
for (const tenant of tenants) {
|
|
30
|
+
await this.runTenantCallback({ callback, tenant });
|
|
31
|
+
}
|
|
32
|
+
return tenants.length;
|
|
33
|
+
}
|
|
34
|
+
/** @type {Array<{error: Error, tenant: ?}>} */
|
|
35
|
+
const failures = [];
|
|
36
|
+
const workers = [];
|
|
37
|
+
let tenantIndex = 0;
|
|
38
|
+
const workerCount = Math.min(this.parallelCount, tenants.length);
|
|
39
|
+
for (let workerIndex = 0; workerIndex < workerCount; workerIndex++) {
|
|
40
|
+
workers.push((async () => {
|
|
41
|
+
while (tenantIndex < tenants.length) {
|
|
42
|
+
const tenant = tenants[tenantIndex];
|
|
43
|
+
tenantIndex++;
|
|
44
|
+
try {
|
|
45
|
+
await this.runTenantCallback({ callback, tenant });
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
failures.push({
|
|
49
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
50
|
+
tenant
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
})());
|
|
55
|
+
}
|
|
56
|
+
await Promise.all(workers);
|
|
57
|
+
if (failures.length > 0) {
|
|
58
|
+
const failedTenantLabels = failures.map((failure) => TenantIterator.tenantLabel(failure.tenant)).join(", ");
|
|
59
|
+
throw new AggregateError(failures.map((failure) => failure.error), `Failed tenant database command for tenant(s): ${failedTenantLabels}`);
|
|
60
|
+
}
|
|
61
|
+
return tenants.length;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Enters one tenant's context and runs the callback, asserting the database is active first.
|
|
65
|
+
* @param {{callback: function({databaseConfiguration: import("../configuration-types.js").DatabaseConfigurationType, tenant: ?}) : Promise<void>, tenant: ?}} args
|
|
66
|
+
* @returns {Promise<void>}
|
|
67
|
+
*/
|
|
68
|
+
async runTenantCallback({ callback, tenant }) {
|
|
69
|
+
await this.configuration.runWithTenant(tenant, async () => {
|
|
70
|
+
if (!this.configuration.isDatabaseIdentifierActive(this.identifier)) {
|
|
71
|
+
throw new Error(`Tenant database identifier ${this.identifier} is inactive for tenant: ${TenantIterator.tenantLabel(tenant)}`);
|
|
72
|
+
}
|
|
73
|
+
await callback({
|
|
74
|
+
databaseConfiguration: this.configuration.resolveDatabaseConfiguration(this.identifier),
|
|
75
|
+
tenant
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Builds a human-readable label for a tenant for use in error messages.
|
|
81
|
+
* @param {?} tenant
|
|
82
|
+
* @returns {string}
|
|
83
|
+
*/
|
|
84
|
+
static tenantLabel(tenant) {
|
|
85
|
+
if (tenant && typeof tenant === "object") {
|
|
86
|
+
const tenantObject = /** @type {{id?: ?, name?: ?, slug?: ?}} */ (tenant);
|
|
87
|
+
if (tenantObject.slug)
|
|
88
|
+
return String(tenantObject.slug);
|
|
89
|
+
if (tenantObject.name)
|
|
90
|
+
return String(tenantObject.name);
|
|
91
|
+
if (tenantObject.id)
|
|
92
|
+
return String(tenantObject.id);
|
|
93
|
+
}
|
|
94
|
+
return JSON.stringify(tenant);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGVuYW50LWl0ZXJhdG9yLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL3RlbmFudHMvdGVuYW50LWl0ZXJhdG9yLmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLFlBQVk7QUFFWjs7Ozs7Ozs7R0FRRztBQUNILE1BQU0sQ0FBQyxPQUFPLE9BQU8sY0FBYztJQUNqQzs7O09BR0c7SUFDSCxZQUFZLEVBQUMsYUFBYSxFQUFFLFVBQVUsRUFBRSxhQUFhLEdBQUcsQ0FBQyxFQUFDO1FBQ3hELElBQUksQ0FBQyxhQUFhLEdBQUcsYUFBYSxDQUFBO1FBQ2xDLElBQUksQ0FBQyxVQUFVLEdBQUcsVUFBVSxDQUFBO1FBQzVCLElBQUksQ0FBQyxhQUFhLEdBQUcsYUFBYSxDQUFBO0lBQ3BDLENBQUM7SUFFRDs7Ozs7T0FLRztJQUNILEtBQUssQ0FBQyxHQUFHLENBQUMsT0FBTyxFQUFFLFFBQVE7UUFDekIsSUFBSSxJQUFJLENBQUMsYUFBYSxJQUFJLENBQUMsRUFBRSxDQUFDO1lBQzVCLEtBQUssTUFBTSxNQUFNLElBQUksT0FBTyxFQUFFLENBQUM7Z0JBQzdCLE1BQU0sSUFBSSxDQUFDLGlCQUFpQixDQUFDLEVBQUMsUUFBUSxFQUFFLE1BQU0sRUFBQyxDQUFDLENBQUE7WUFDbEQsQ0FBQztZQUVELE9BQU8sT0FBTyxDQUFDLE1BQU0sQ0FBQTtRQUN2QixDQUFDO1FBRUQsK0NBQStDO1FBQy9DLE1BQU0sUUFBUSxHQUFHLEVBQUUsQ0FBQTtRQUNuQixNQUFNLE9BQU8sR0FBRyxFQUFFLENBQUE7UUFDbEIsSUFBSSxXQUFXLEdBQUcsQ0FBQyxDQUFBO1FBQ25CLE1BQU0sV0FBVyxHQUFHLElBQUksQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLGFBQWEsRUFBRSxPQUFPLENBQUMsTUFBTSxDQUFDLENBQUE7UUFFaEUsS0FBSyxJQUFJLFdBQVcsR0FBRyxDQUFDLEVBQUUsV0FBVyxHQUFHLFdBQVcsRUFBRSxXQUFXLEVBQUUsRUFB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QU0sQ0FBQyxDQUFBO0lBQy9CLENBQUM7Q0FDRiIsInNvdXJjZXNDb250ZW50IjpbIi8vIEB0cy1jaGVja1xuXG4vKipcbiAqIFJ1bnMgYSBjYWxsYmFjayBvbmNlIHdpdGhpbiBlYWNoIHRlbmFudCdzIGNvbnRleHQgZm9yIGEgdGVuYW50IGRhdGFiYXNlIGlkZW50aWZpZXIsXG4gKiBvcHRpb25hbGx5IHNldmVyYWwgdGVuYW50cyBhdCBhIHRpbWUuIEVhY2ggdGVuYW50IGlzIGVudGVyZWQgd2l0aCBgcnVuV2l0aFRlbmFudGAsIGFuZCBhXG4gKiB0ZW5hbnQgd2hvc2UgZGF0YWJhc2UgaWRlbnRpZmllciBpcyBpbmFjdGl2ZSB0aHJvd3MgcmF0aGVyIHRoYW4gcnVubmluZyB0aGUgY2FsbGJhY2tcbiAqIGFnYWluc3QgdGhlIHdyb25nIGNvbm5lY3Rpb24uIFdoZW4gaXRlcmF0aW5nIGluIHBhcmFsbGVsIHRoZSBwZXItdGVuYW50IGZhaWx1cmVzIGFyZVxuICogY29sbGVjdGVkIGFuZCByZXRocm93biB0b2dldGhlciBhcyBhbiBgQWdncmVnYXRlRXJyb3JgIHNvIG9uZSBiYWQgdGVuYW50IGRvZXMgbm90IGhpZGUgdGhlXG4gKiBvdGhlcnMuIFRoaXMgaXMgdGhlIGl0ZXJhdGlvbiBlbmdpbmUgc2hhcmVkIGJ5IHRoZSBgZGI6dGVuYW50czoqYCBDTEkgY29tbWFuZHMgYW5kIHRoZVxuICogcnVudGltZSB7QGxpbmsgVGVuYW50fSBmYcOnYWRlOyB0aGUgY2FsbGVyIGlzIHJlc3BvbnNpYmxlIGZvciBwcm9kdWNpbmcgdGhlIHRlbmFudCBsaXN0LlxuICovXG5leHBvcnQgZGVmYXVsdCBjbGFzcyBUZW5hbnRJdGVyYXRvciB7XG4gIC8qKlxuICAgKiBDcmVhdGVzIGFuIGl0ZXJhdG9yIGJvdW5kIHRvIGEgY29uZmlndXJhdGlvbiBhbmQgdGVuYW50IGRhdGFiYXNlIGlkZW50aWZpZXIuXG4gICAqIEBwYXJhbSB7e2NvbmZpZ3VyYXRpb246IGltcG9ydChcIi4uL2NvbmZpZ3VyYXRpb24uanNcIikuZGVmYXVsdCwgaWRlbnRpZmllcjogc3RyaW5nLCBwYXJhbGxlbENvdW50PzogbnVtYmVyfX0gYXJnc1xuICAgKi9cbiAgY29uc3RydWN0b3Ioe2NvbmZpZ3VyYXRpb24sIGlkZW50aWZpZXIsIHBhcmFsbGVsQ291bnQgPSAxfSkge1xuICAgIHRoaXMuY29uZmlndXJhdGlvbiA9IGNvbmZpZ3VyYXRpb25cbiAgICB0aGlzLmlkZW50aWZpZXIgPSBpZGVudGlmaWVyXG4gICAgdGhpcy5wYXJhbGxlbENvdW50ID0gcGFyYWxsZWxDb3VudFxuICB9XG5cbiAgLyoqXG4gICAqIFJ1bnMgYGNhbGxiYWNrYCB3aXRoaW4gZWFjaCB0ZW5hbnQncyBjb250ZXh0IGFuZCByZXR1cm5zIGhvdyBtYW55IHRlbmFudHMgd2VyZSBwcm9jZXNzZWQuXG4gICAqIEBwYXJhbSB7QXJyYXk8Pz59IHRlbmFudHNcbiAgICogQHBhcmFtIHtmdW5jdGlvbih7ZGF0YWJhc2VDb25maWd1cmF0aW9uOiBpbXBvcnQoXCIuLi9jb25maWd1cmF0aW9uLXR5cGVzLmpzXCIpLkRhdGFiYXNlQ29uZmlndXJhdGlvblR5cGUsIHRlbmFudDogP30pIDogUHJvbWlzZTx2b2lkPn0gY2FsbGJhY2tcbiAgICogQHJldHVybnMge1Byb21pc2U8bnVtYmVyPn1cbiAgICovXG4gIGFzeW5jIHJ1bih0ZW5hbnRzLCBjYWxsYmFjaykge1xuICAgIGlmICh0aGlzLnBhcmFsbGVsQ291bnQgPD0gMSkge1xuICAgICAgZm9yIChjb25zdCB0ZW5hbnQgb2YgdGVuYW50cykge1xuICAgICAgICBhd2FpdCB0aGlzLnJ1blRlbmFudENhbGxiYWNrKHtjYWxsYmFjaywgdGVuYW50fSlcbiAgICAgIH1cblxuICAgICAgcmV0dXJuIHRlbmFudHMubGVuZ3RoXG4gICAgfVxuXG4gICAgLyoqIEB0eXBlIHtBcnJheTx7ZXJyb3I6IEVycm9yLCB0ZW5hbnQ6ID99Pn0gKi9cbiAgICBjb25zdCBmYWlsdXJlcyA9IFtdXG4gICAgY29uc3Qgd29ya2VycyA9IFtdXG4gICAgbGV0IHRlbmFudEluZGV4ID0gMFxuICAgIGNvbnN0IHdvcmtlckNvdW50ID0gTWF0aC5taW4odGhpcy5wYXJhbGxlbENvdW50LCB0ZW5hbnRzLmxlbmd0aClcblxuICAgIGZvciAobGV0IHdvcmtlckluZGV4ID0gMDsgd29ya2VySW5kZXggPCB3b3JrZXJDb3VudDsgd29ya2VySW5kZXgrKykge1xuICAgICAgd29ya2Vycy5wdXNoKChhc3luYyAoKSA9PiB7XG4gICAgICAgIHdoaWxlICh0ZW5hbnRJbmRleCA8IHRlbmFudHMubGVuZ3RoKSB7XG4gICAgICAgICAgY29uc3QgdGVuYW50ID0gdGVuYW50c1t0ZW5hbnRJbmRleF1cblxuICAgICAgICAgIHRlbmFudEluZGV4KytcblxuICAgICAgICAgIHRyeSB7XG4gICAgICAgICAgICBhd2FpdCB0aGlzLnJ1blRlbmFudENhbGxiYWNrKHtjYWxsYmFjaywgdGVuYW50fSlcbiAgICAgICAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgICAgICAgZmFpbHVyZXMucHVzaCh7XG4gICAgICAgICAgICAgIGVycm9yOiBlcnJvciBpbnN0YW5jZW9mIEVycm9yID8gZXJyb3IgOiBuZXcgRXJyb3IoU3RyaW5nKGVycm9yKSksXG4gICAgICAgICAgICAgIHRlbmFudFxuICAgICAgICAgICAgfSlcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH0pKCkpXG4gICAgfVxuXG4gICAgYXdhaXQgUHJvbWlzZS5hbGwod29ya2VycylcblxuICAgIGlmIChmYWlsdXJlcy5sZW5ndGggPiAwKSB7XG4gICAgICBjb25zdCBmYWlsZWRUZW5hbnRMYWJlbHMgPSBmYWlsdXJlcy5tYXAoKGZhaWx1cmUpID0+IFRlbmFudEl0ZXJhdG9yLnRlbmFudExhYmVsKGZhaWx1cmUudGVuYW50KSkuam9pbihcIiwgXCIpXG5cbiAgICAgIHRocm93IG5ldyBBZ2dyZWdhdGVFcnJvcihcbiAgICAgICAgZmFpbHVyZXMubWFwKChmYWlsdXJlKSA9PiBmYWlsdXJlLmVycm9yKSxcbiAgICAgICAgYEZhaWxlZCB0ZW5hbnQgZGF0YWJhc2UgY29tbWFuZCBmb3IgdGVuYW50KHMpOiAke2ZhaWxlZFRlbmFudExhYmVsc31gXG4gICAgICApXG4gICAgfVxuXG4gICAgcmV0dXJuIHRlbmFudHMubGVuZ3RoXG4gIH1cblxuICAvKipcbiAgICogRW50ZXJzIG9uZSB0ZW5hbnQncyBjb250ZXh0IGFuZCBydW5zIHRoZSBjYWxsYmFjaywgYXNzZXJ0aW5nIHRoZSBkYXRhYmFzZSBpcyBhY3RpdmUgZmlyc3QuXG4gICAqIEBwYXJhbSB7e2NhbGxiYWNrOiBmdW5jdGlvbih7ZGF0YWJhc2VDb25maWd1cmF0aW9uOiBpbXBvcnQoXCIuLi9jb25maWd1cmF0aW9uLXR5cGVzLmpzXCIpLkRhdGFiYXNlQ29uZmlndXJhdGlvblR5cGUsIHRlbmFudDogP30pIDogUHJvbWlzZTx2b2lkPiwgdGVuYW50OiA/fX0gYXJnc1xuICAgKiBAcmV0dXJucyB7UHJvbWlzZTx2b2lkPn1cbiAgICovXG4gIGFzeW5jIHJ1blRlbmFudENhbGxiYWNrKHtjYWxsYmFjaywgdGVuYW50fSkge1xuICAgIGF3YWl0IHRoaXMuY29uZmlndXJhdGlvbi5ydW5XaXRoVGVuYW50KHRlbmFudCwgYXN5bmMgKCkgPT4ge1xuICAgICAgaWYgKCF0aGlzLmNvbmZpZ3VyYXRpb24uaXNEYXRhYmFzZUlkZW50aWZpZXJBY3RpdmUodGhpcy5pZGVudGlmaWVyKSkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYFRlbmFudCBkYXRhYmFzZSBpZGVudGlmaWVyICR7dGhpcy5pZGVudGlmaWVyfSBpcyBpbmFjdGl2ZSBmb3IgdGVuYW50OiAke1RlbmFudEl0ZXJhdG9yLnRlbmFudExhYmVsKHRlbmFudCl9YClcbiAgICAgIH1cblxuICAgICAgYXdhaXQgY2FsbGJhY2soe1xuICAgICAgICBkYXRhYmFzZUNvbmZpZ3VyYXRpb246IHRoaXMuY29uZmlndXJhdGlvbi5yZXNvbHZlRGF0YWJhc2VDb25maWd1cmF0aW9uKHRoaXMuaWRlbnRpZmllciksXG4gICAgICAgIHRlbmFudFxuICAgICAgfSlcbiAgICB9KVxuICB9XG5cbiAgLyoqXG4gICAqIEJ1aWxkcyBhIGh1bWFuLXJlYWRhYmxlIGxhYmVsIGZvciBhIHRlbmFudCBmb3IgdXNlIGluIGVycm9yIG1lc3NhZ2VzLlxuICAgKiBAcGFyYW0gez99IHRlbmFudFxuICAgKiBAcmV0dXJucyB7c3RyaW5nfVxuICAgKi9cbiAgc3RhdGljIHRlbmFudExhYmVsKHRlbmFudCkge1xuICAgIGlmICh0ZW5hbnQgJiYgdHlwZW9mIHRlbmFudCA9PT0gXCJvYmplY3RcIikge1xuICAgICAgY29uc3QgdGVuYW50T2JqZWN0ID0gLyoqIEB0eXBlIHt7aWQ/OiA/LCBuYW1lPzogPywgc2x1Zz86ID99fSAqLyAodGVuYW50KVxuXG4gICAgICBpZiAodGVuYW50T2JqZWN0LnNsdWcpIHJldHVybiBTdHJpbmcodGVuYW50T2JqZWN0LnNsdWcpXG4gICAgICBpZiAodGVuYW50T2JqZWN0Lm5hbWUpIHJldHVybiBTdHJpbmcodGVuYW50T2JqZWN0Lm5hbWUpXG4gICAgICBpZiAodGVuYW50T2JqZWN0LmlkKSByZXR1cm4gU3RyaW5nKHRlbmFudE9iamVjdC5pZClcbiAgICB9XG5cbiAgICByZXR1cm4gSlNPTi5zdHJpbmdpZnkodGVuYW50KVxuICB9XG59XG4iXX0=
|