velocious 1.0.481 → 1.0.482

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,390 @@
1
+ // @ts-check
2
+
3
+ import MigrationsLedger from "../migrations-ledger.js"
4
+ import TableData from "../table-data/index.js"
5
+ import TableIndex from "../table-data/table-index.js"
6
+
7
+ const SIMPLE_DEFAULT_PATTERN = /^(?:-?\d+(?:\.\d+)?|[A-Za-z0-9 _.,:/@+-]*)$/
8
+
9
+ /** @type {Record<string, number>} */
10
+ const TEXT_TYPE_RANKS = {
11
+ tinytext: 1,
12
+ text: 2,
13
+ mediumtext: 3,
14
+ longtext: 4
15
+ }
16
+
17
+ /**
18
+ * Clones table structure (columns, text-type widening, indexes) from a source database
19
+ * into a target database for a given set of tables, then baselines the target's
20
+ * `schema_migrations` ledger to the source via {@link MigrationsLedger}. This is the
21
+ * mechanism multi-tenant apps use to provision a tenant database from a template/global
22
+ * database without re-running migrations: the structure is copied and the ledger is
23
+ * recorded as already-applied, so a later `db:tenants:migrate` does not re-run an
24
+ * `addColumn` whose column already exists.
25
+ *
26
+ * The cloner is intentionally policy-free — the caller decides which tables to sync and
27
+ * which databases are source/target. It is idempotent: missing tables are created,
28
+ * missing columns added, too-narrow text columns widened, and missing indexes created;
29
+ * an index whose definition diverges from the source is treated as drift and throws.
30
+ */
31
+ export default class SchemaCloner {
32
+ /**
33
+ * Creates a cloner that copies table structure from `sourceDb` into `targetDb`.
34
+ * @param {{sourceDb: import("../drivers/base.js").default, targetDb: import("../drivers/base.js").default}} args
35
+ */
36
+ constructor({sourceDb, targetDb}) {
37
+ this.sourceDb = sourceDb
38
+ this.targetDb = targetDb
39
+ }
40
+
41
+ /**
42
+ * Clones every given table from the source into the target, then baselines the
43
+ * target's ledger so the cloned schema is recorded as already-migrated.
44
+ * @param {string[]} tableNames
45
+ * @returns {Promise<void>}
46
+ */
47
+ async syncTables(tableNames) {
48
+ for (const tableName of tableNames) {
49
+ await this.syncTable(tableName)
50
+ }
51
+
52
+ await this.reconcileLedger()
53
+ }
54
+
55
+ /**
56
+ * Clones a single table from the source into the target, creating it or adding and
57
+ * widening columns and indexes as needed.
58
+ * @param {string} tableName
59
+ * @returns {Promise<void>}
60
+ */
61
+ async syncTable(tableName) {
62
+ const sourceTable = await this.sourceDb.getTableByName(tableName)
63
+
64
+ if (!sourceTable) {
65
+ throw new Error(`Expected source table to exist: ${tableName}`)
66
+ }
67
+
68
+ if (!await this.targetDb.tableExists(tableName)) {
69
+ await this.createTargetTable({sourceTable, tableName})
70
+ return
71
+ }
72
+
73
+ const changedColumns = await this.ensureTargetColumns({sourceTable, tableName})
74
+
75
+ if (changedColumns) {
76
+ this.targetDb.clearSchemaCache()
77
+ }
78
+
79
+ await this.ensureTargetIndexes({sourceTable, tableName})
80
+ }
81
+
82
+ /**
83
+ * Creates the table in the target from the source table's columns and its
84
+ * non-primary-key indexes.
85
+ * @param {{sourceTable: import("../drivers/base-table.js").default, tableName: string}} args
86
+ * @returns {Promise<void>}
87
+ */
88
+ async createTargetTable({sourceTable, tableName}) {
89
+ const tableData = new TableData(tableName)
90
+
91
+ for (const sourceColumn of await sourceTable.getColumns()) {
92
+ tableData.addColumn(sourceColumn.getName(), this.columnArgsFromSourceColumn(sourceColumn, {isNewColumn: false}))
93
+ }
94
+
95
+ for (const sourceIndex of await sourceTable.getIndexes()) {
96
+ if (!sourceIndex.isPrimaryKey()) {
97
+ tableData.addIndex(this.tableDataIndexFromSourceIndex(sourceIndex))
98
+ }
99
+ }
100
+
101
+ await this.targetDb.createTable(tableData)
102
+ this.targetDb.clearSchemaCache()
103
+ }
104
+
105
+ /**
106
+ * Adds columns present on the source but missing from the target, and widens
107
+ * too-narrow target text columns.
108
+ * @param {{sourceTable: import("../drivers/base-table.js").default, tableName: string}} args
109
+ * @returns {Promise<boolean>} Whether any column was added or widened.
110
+ */
111
+ async ensureTargetColumns({sourceTable, tableName}) {
112
+ const sourceColumns = await sourceTable.getColumns()
113
+ const targetTable = await this.targetDb.getTableByNameOrFail(tableName)
114
+ const targetColumnsByName = new Map()
115
+ const missingColumns = []
116
+ const columnsNeedingWidening = []
117
+
118
+ for (const targetColumn of await targetTable.getColumns()) {
119
+ targetColumnsByName.set(targetColumn.getName(), targetColumn)
120
+ }
121
+
122
+ for (const sourceColumn of sourceColumns) {
123
+ const targetColumn = targetColumnsByName.get(sourceColumn.getName())
124
+
125
+ if (!targetColumn) {
126
+ missingColumns.push(sourceColumn)
127
+ } else if (this.columnNeedsWidening(sourceColumn, targetColumn)) {
128
+ columnsNeedingWidening.push(sourceColumn)
129
+ }
130
+ }
131
+
132
+ if (missingColumns.length <= 0 && columnsNeedingWidening.length <= 0) {
133
+ return false
134
+ }
135
+
136
+ const tableData = new TableData(tableName)
137
+
138
+ for (const sourceColumn of missingColumns) {
139
+ tableData.addColumn(sourceColumn.getName(), this.columnArgsFromSourceColumn(sourceColumn, {isNewColumn: true}))
140
+ }
141
+
142
+ for (const sourceColumn of columnsNeedingWidening) {
143
+ tableData.addColumn(sourceColumn.getName(), this.columnArgsFromSourceColumn(sourceColumn, {isNewColumn: false}))
144
+ }
145
+
146
+ for (const alterSql of await this.targetDb.alterTableSQLs(tableData)) {
147
+ await this.targetDb.query(alterSql)
148
+ }
149
+
150
+ return true
151
+ }
152
+
153
+ /**
154
+ * Creates non-primary-key indexes present on the source but missing from the target,
155
+ * and throws if an existing target index diverges from the source.
156
+ * @param {{sourceTable: import("../drivers/base-table.js").default, tableName: string}} args
157
+ * @returns {Promise<void>}
158
+ */
159
+ async ensureTargetIndexes({sourceTable, tableName}) {
160
+ const targetTable = await this.targetDb.getTableByNameOrFail(tableName)
161
+ /** @type {Map<string, import("../drivers/base-columns-index.js").default>} */
162
+ const targetIndexesByName = new Map()
163
+ const targetIndexSignatures = new Set()
164
+ let createdIndex = false
165
+
166
+ for (const targetIndex of await targetTable.getIndexes()) {
167
+ targetIndexesByName.set(targetIndex.getName(), targetIndex)
168
+ targetIndexSignatures.add(this.indexSignature(targetIndex))
169
+ }
170
+
171
+ for (const sourceIndex of await sourceTable.getIndexes()) {
172
+ if (sourceIndex.isPrimaryKey()) {
173
+ continue
174
+ }
175
+
176
+ const sourceIndexSignature = this.indexSignature(sourceIndex)
177
+
178
+ // SQLite index names are unique per-database, not per-table, so match cloned
179
+ // indexes by their column/uniqueness signature rather than their name.
180
+ if (this.targetDb.getType() === "sqlite" && targetIndexSignatures.has(sourceIndexSignature)) {
181
+ continue
182
+ }
183
+
184
+ const targetIndex = this.targetDb.getType() === "sqlite" ? undefined : targetIndexesByName.get(sourceIndex.getName())
185
+
186
+ if (!targetIndex) {
187
+ const createIndexSqls = await this.targetDb.createIndexSQLs(this.createIndexArgsFromSourceIndex({sourceIndex, tableName}))
188
+
189
+ for (const createIndexSql of createIndexSqls) {
190
+ await this.targetDb.query(createIndexSql)
191
+ }
192
+
193
+ createdIndex = true
194
+ targetIndexSignatures.add(sourceIndexSignature)
195
+ continue
196
+ }
197
+
198
+ if (!this.indexesMatch(sourceIndex, targetIndex)) {
199
+ throw new Error(`Schema clone index drift for ${tableName}.${sourceIndex.getName()}`)
200
+ }
201
+ }
202
+
203
+ if (createdIndex) {
204
+ this.targetDb.clearSchemaCache()
205
+ }
206
+ }
207
+
208
+ /**
209
+ * Baselines the target ledger so the cloned schema is recorded as already-applied.
210
+ * @returns {Promise<string[]>} The versions newly recorded on the target.
211
+ */
212
+ async reconcileLedger() {
213
+ return await MigrationsLedger.baselineFromDatabase({sourceDb: this.sourceDb, targetDb: this.targetDb})
214
+ }
215
+
216
+ /**
217
+ * Whether the target ledger is missing any version applied on the source — i.e. the
218
+ * target schema may have been advanced out of band without recording it.
219
+ * @returns {Promise<boolean>}
220
+ */
221
+ async ledgerDriftsFromSource() {
222
+ if (!await MigrationsLedger.tableExists(this.targetDb)) {
223
+ return true
224
+ }
225
+
226
+ const sourceVersions = await MigrationsLedger.appliedVersions(this.sourceDb)
227
+ const targetVersionSet = new Set(await MigrationsLedger.appliedVersions(this.targetDb))
228
+
229
+ return sourceVersions.some((version) => !targetVersionSet.has(version))
230
+ }
231
+
232
+ /**
233
+ * Maps a source index into a TableData index for table creation (SQLite omits the
234
+ * index name so the driver can generate a unique one).
235
+ * @param {import("../drivers/base-columns-index.js").default} sourceIndex
236
+ * @returns {TableIndex}
237
+ */
238
+ tableDataIndexFromSourceIndex(sourceIndex) {
239
+ /** @type {{name?: string, unique: boolean}} */
240
+ const args = {unique: sourceIndex.isUnique()}
241
+
242
+ // SQLite index names are unique per-database, not per-table, so let the driver
243
+ // generate one; other drivers preserve the source index name. Build the TableIndex
244
+ // directly (rather than via the driver's getTableDataIndex, which only MySQL and
245
+ // SQLite implement) so cloning a PostgreSQL or MS-SQL source table works too.
246
+ if (this.targetDb.getType() !== "sqlite") {
247
+ args.name = sourceIndex.getName()
248
+ }
249
+
250
+ return new TableIndex(sourceIndex.getColumnNames(), args)
251
+ }
252
+
253
+ /**
254
+ * Builds driver create-index args from a source index (the index name is omitted on
255
+ * SQLite, where index names are unique per-database rather than per-table).
256
+ * @param {{sourceIndex: import("../drivers/base-columns-index.js").default, tableName: string}} args
257
+ * @returns {{columns: string[], name?: string, tableName: string, unique: boolean}}
258
+ */
259
+ createIndexArgsFromSourceIndex({sourceIndex, tableName}) {
260
+ /** @type {{columns: string[], name?: string, tableName: string, unique: boolean}} */
261
+ const createIndexArgs = {
262
+ columns: sourceIndex.getColumnNames(),
263
+ tableName,
264
+ unique: sourceIndex.isUnique()
265
+ }
266
+
267
+ if (this.targetDb.getType() !== "sqlite") {
268
+ createIndexArgs.name = sourceIndex.getName()
269
+ }
270
+
271
+ return createIndexArgs
272
+ }
273
+
274
+ /**
275
+ * Whether two indexes have the same uniqueness and ordered column list.
276
+ * @param {import("../drivers/base-columns-index.js").default} sourceIndex
277
+ * @param {import("../drivers/base-columns-index.js").default} targetIndex
278
+ * @returns {boolean}
279
+ */
280
+ indexesMatch(sourceIndex, targetIndex) {
281
+ const sourceColumnNames = sourceIndex.getColumnNames()
282
+ const targetColumnNames = targetIndex.getColumnNames()
283
+
284
+ if (sourceIndex.isUnique() !== targetIndex.isUnique()) {
285
+ return false
286
+ }
287
+
288
+ if (sourceColumnNames.length !== targetColumnNames.length) {
289
+ return false
290
+ }
291
+
292
+ for (let columnIndex = 0; columnIndex < sourceColumnNames.length; columnIndex++) {
293
+ if (sourceColumnNames[columnIndex] !== targetColumnNames[columnIndex]) {
294
+ return false
295
+ }
296
+ }
297
+
298
+ return true
299
+ }
300
+
301
+ /**
302
+ * A stable signature for an index, used to match cloned indexes by shape.
303
+ * @param {import("../drivers/base-columns-index.js").default} index
304
+ * @returns {string}
305
+ */
306
+ indexSignature(index) {
307
+ return `${index.isUnique() ? "unique" : "index"}:${index.getColumnNames().join(",")}`
308
+ }
309
+
310
+ /**
311
+ * Normalizes a column type to its canonical lowercase form (`int` becomes `integer`).
312
+ * @param {string} columnType
313
+ * @returns {string}
314
+ */
315
+ normalizedColumnType(columnType) {
316
+ const normalizedType = columnType.toLowerCase()
317
+
318
+ if (normalizedType === "int") {
319
+ return "integer"
320
+ }
321
+
322
+ return normalizedType
323
+ }
324
+
325
+ /**
326
+ * The widening rank of a text column type (0 when not a text type).
327
+ * @param {string} columnType
328
+ * @returns {number}
329
+ */
330
+ textTypeRank(columnType) {
331
+ return TEXT_TYPE_RANKS[this.normalizedColumnType(columnType)] || 0
332
+ }
333
+
334
+ /**
335
+ * Whether the target's text column is narrower than the source's and must be widened.
336
+ * @param {import("../drivers/base-column.js").default} sourceColumn
337
+ * @param {import("../drivers/base-column.js").default} targetColumn
338
+ * @returns {boolean}
339
+ */
340
+ columnNeedsWidening(sourceColumn, targetColumn) {
341
+ const sourceRank = this.textTypeRank(sourceColumn.getType())
342
+ const targetRank = this.textTypeRank(targetColumn.getType())
343
+
344
+ return sourceRank > 0 && targetRank > 0 && sourceRank > targetRank
345
+ }
346
+
347
+ /**
348
+ * Builds TableData column args from a source column, copying type, nullability,
349
+ * length, notes, simple defaults and (for full clones) primary-key flag.
350
+ * @param {import("../drivers/base-column.js").default} sourceColumn
351
+ * @param {{isNewColumn: boolean}} args
352
+ * @returns {Record<string, unknown>}
353
+ */
354
+ columnArgsFromSourceColumn(sourceColumn, {isNewColumn}) {
355
+ /** @type {{autoIncrement?: boolean, default?: unknown, isNewColumn: boolean, maxLength?: number, notes?: string, null: boolean, primaryKey?: boolean, type: string}} */
356
+ const columnArgs = {
357
+ isNewColumn,
358
+ null: sourceColumn.getNull(),
359
+ type: this.normalizedColumnType(sourceColumn.getType())
360
+ }
361
+ const defaultValue = sourceColumn.getDefault()
362
+ const maxLength = sourceColumn.getMaxLength()
363
+ const notes = sourceColumn.getNotes()
364
+
365
+ if (!isNewColumn && sourceColumn.getPrimaryKey()) {
366
+ columnArgs.primaryKey = true
367
+ }
368
+
369
+ if (sourceColumn.getAutoIncrement()) {
370
+ columnArgs.autoIncrement = true
371
+ }
372
+
373
+ // A maxLength of -1 is the MS-SQL "max" sentinel (NVARCHAR(MAX) / VARBINARY(MAX),
374
+ // backing Velocious text/json/blob columns); the column type drives the unbounded
375
+ // SQL, so don't forward -1 as an explicit length (it would emit NVARCHAR(-1)).
376
+ if (maxLength !== undefined && maxLength >= 0) {
377
+ columnArgs.maxLength = maxLength
378
+ }
379
+
380
+ if (notes) {
381
+ columnArgs.notes = notes
382
+ }
383
+
384
+ if (defaultValue !== null && defaultValue !== undefined && SIMPLE_DEFAULT_PATTERN.test(String(defaultValue))) {
385
+ columnArgs.default = defaultValue
386
+ }
387
+
388
+ return columnArgs
389
+ }
390
+ }
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Clones table structure (columns, text-type widening, indexes) from a source database
3
+ * into a target database for a given set of tables, then baselines the target's
4
+ * `schema_migrations` ledger to the source via {@link MigrationsLedger}. This is the
5
+ * mechanism multi-tenant apps use to provision a tenant database from a template/global
6
+ * database without re-running migrations: the structure is copied and the ledger is
7
+ * recorded as already-applied, so a later `db:tenants:migrate` does not re-run an
8
+ * `addColumn` whose column already exists.
9
+ *
10
+ * The cloner is intentionally policy-free — the caller decides which tables to sync and
11
+ * which databases are source/target. It is idempotent: missing tables are created,
12
+ * missing columns added, too-narrow text columns widened, and missing indexes created;
13
+ * an index whose definition diverges from the source is treated as drift and throws.
14
+ */
15
+ export default class SchemaCloner {
16
+ /**
17
+ * Creates a cloner that copies table structure from `sourceDb` into `targetDb`.
18
+ * @param {{sourceDb: import("../drivers/base.js").default, targetDb: import("../drivers/base.js").default}} args
19
+ */
20
+ constructor({ sourceDb, targetDb }: {
21
+ sourceDb: import("../drivers/base.js").default;
22
+ targetDb: import("../drivers/base.js").default;
23
+ });
24
+ sourceDb: import("../drivers/base.js").default;
25
+ targetDb: import("../drivers/base.js").default;
26
+ /**
27
+ * Clones every given table from the source into the target, then baselines the
28
+ * target's ledger so the cloned schema is recorded as already-migrated.
29
+ * @param {string[]} tableNames
30
+ * @returns {Promise<void>}
31
+ */
32
+ syncTables(tableNames: string[]): Promise<void>;
33
+ /**
34
+ * Clones a single table from the source into the target, creating it or adding and
35
+ * widening columns and indexes as needed.
36
+ * @param {string} tableName
37
+ * @returns {Promise<void>}
38
+ */
39
+ syncTable(tableName: string): Promise<void>;
40
+ /**
41
+ * Creates the table in the target from the source table's columns and its
42
+ * non-primary-key indexes.
43
+ * @param {{sourceTable: import("../drivers/base-table.js").default, tableName: string}} args
44
+ * @returns {Promise<void>}
45
+ */
46
+ createTargetTable({ sourceTable, tableName }: {
47
+ sourceTable: import("../drivers/base-table.js").default;
48
+ tableName: string;
49
+ }): Promise<void>;
50
+ /**
51
+ * Adds columns present on the source but missing from the target, and widens
52
+ * too-narrow target text columns.
53
+ * @param {{sourceTable: import("../drivers/base-table.js").default, tableName: string}} args
54
+ * @returns {Promise<boolean>} Whether any column was added or widened.
55
+ */
56
+ ensureTargetColumns({ sourceTable, tableName }: {
57
+ sourceTable: import("../drivers/base-table.js").default;
58
+ tableName: string;
59
+ }): Promise<boolean>;
60
+ /**
61
+ * Creates non-primary-key indexes present on the source but missing from the target,
62
+ * and throws if an existing target index diverges from the source.
63
+ * @param {{sourceTable: import("../drivers/base-table.js").default, tableName: string}} args
64
+ * @returns {Promise<void>}
65
+ */
66
+ ensureTargetIndexes({ sourceTable, tableName }: {
67
+ sourceTable: import("../drivers/base-table.js").default;
68
+ tableName: string;
69
+ }): Promise<void>;
70
+ /**
71
+ * Baselines the target ledger so the cloned schema is recorded as already-applied.
72
+ * @returns {Promise<string[]>} The versions newly recorded on the target.
73
+ */
74
+ reconcileLedger(): Promise<string[]>;
75
+ /**
76
+ * Whether the target ledger is missing any version applied on the source — i.e. the
77
+ * target schema may have been advanced out of band without recording it.
78
+ * @returns {Promise<boolean>}
79
+ */
80
+ ledgerDriftsFromSource(): Promise<boolean>;
81
+ /**
82
+ * Maps a source index into a TableData index for table creation (SQLite omits the
83
+ * index name so the driver can generate a unique one).
84
+ * @param {import("../drivers/base-columns-index.js").default} sourceIndex
85
+ * @returns {TableIndex}
86
+ */
87
+ tableDataIndexFromSourceIndex(sourceIndex: import("../drivers/base-columns-index.js").default): TableIndex;
88
+ /**
89
+ * Builds driver create-index args from a source index (the index name is omitted on
90
+ * SQLite, where index names are unique per-database rather than per-table).
91
+ * @param {{sourceIndex: import("../drivers/base-columns-index.js").default, tableName: string}} args
92
+ * @returns {{columns: string[], name?: string, tableName: string, unique: boolean}}
93
+ */
94
+ createIndexArgsFromSourceIndex({ sourceIndex, tableName }: {
95
+ sourceIndex: import("../drivers/base-columns-index.js").default;
96
+ tableName: string;
97
+ }): {
98
+ columns: string[];
99
+ name?: string;
100
+ tableName: string;
101
+ unique: boolean;
102
+ };
103
+ /**
104
+ * Whether two indexes have the same uniqueness and ordered column list.
105
+ * @param {import("../drivers/base-columns-index.js").default} sourceIndex
106
+ * @param {import("../drivers/base-columns-index.js").default} targetIndex
107
+ * @returns {boolean}
108
+ */
109
+ indexesMatch(sourceIndex: import("../drivers/base-columns-index.js").default, targetIndex: import("../drivers/base-columns-index.js").default): boolean;
110
+ /**
111
+ * A stable signature for an index, used to match cloned indexes by shape.
112
+ * @param {import("../drivers/base-columns-index.js").default} index
113
+ * @returns {string}
114
+ */
115
+ indexSignature(index: import("../drivers/base-columns-index.js").default): string;
116
+ /**
117
+ * Normalizes a column type to its canonical lowercase form (`int` becomes `integer`).
118
+ * @param {string} columnType
119
+ * @returns {string}
120
+ */
121
+ normalizedColumnType(columnType: string): string;
122
+ /**
123
+ * The widening rank of a text column type (0 when not a text type).
124
+ * @param {string} columnType
125
+ * @returns {number}
126
+ */
127
+ textTypeRank(columnType: string): number;
128
+ /**
129
+ * Whether the target's text column is narrower than the source's and must be widened.
130
+ * @param {import("../drivers/base-column.js").default} sourceColumn
131
+ * @param {import("../drivers/base-column.js").default} targetColumn
132
+ * @returns {boolean}
133
+ */
134
+ columnNeedsWidening(sourceColumn: import("../drivers/base-column.js").default, targetColumn: import("../drivers/base-column.js").default): boolean;
135
+ /**
136
+ * Builds TableData column args from a source column, copying type, nullability,
137
+ * length, notes, simple defaults and (for full clones) primary-key flag.
138
+ * @param {import("../drivers/base-column.js").default} sourceColumn
139
+ * @param {{isNewColumn: boolean}} args
140
+ * @returns {Record<string, unknown>}
141
+ */
142
+ columnArgsFromSourceColumn(sourceColumn: import("../drivers/base-column.js").default, { isNewColumn }: {
143
+ isNewColumn: boolean;
144
+ }): Record<string, unknown>;
145
+ }
146
+ import TableIndex from "../table-data/table-index.js";
147
+ //# sourceMappingURL=schema-cloner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema-cloner.d.ts","sourceRoot":"","sources":["../../../../src/database/tenants/schema-cloner.js"],"names":[],"mappings":"AAgBA;;;;;;;;;;;;;GAaG;AACH;IACE;;;OAGG;IACH,oCAFW;QAAC,QAAQ,EAAE,OAAO,oBAAoB,EAAE,OAAO,CAAC;QAAC,QAAQ,EAAE,OAAO,oBAAoB,EAAE,OAAO,CAAA;KAAC,EAK1G;IAFC,+CAAwB;IACxB,+CAAwB;IAG1B;;;;;OAKG;IACH,uBAHW,MAAM,EAAE,GACN,OAAO,CAAC,IAAI,CAAC,CAQzB;IAED;;;;;OAKG;IACH,qBAHW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAqBzB;IAED;;;;;OAKG;IACH,8CAHW;QAAC,WAAW,EAAE,OAAO,0BAA0B,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAC,GAC1E,OAAO,CAAC,IAAI,CAAC,CAiBzB;IAED;;;;;OAKG;IACH,gDAHW;QAAC,WAAW,EAAE,OAAO,0BAA0B,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAC,GAC1E,OAAO,CAAC,OAAO,CAAC,CA0C5B;IAED;;;;;OAKG;IACH,gDAHW;QAAC,WAAW,EAAE,OAAO,0BAA0B,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAC,GAC1E,OAAO,CAAC,IAAI,CAAC,CAiDzB;IAED;;;OAGG;IACH,mBAFa,OAAO,CAAC,MAAM,EAAE,CAAC,CAI7B;IAED;;;;OAIG;IACH,0BAFa,OAAO,CAAC,OAAO,CAAC,CAW5B;IAED;;;;;OAKG;IACH,2CAHW,OAAO,kCAAkC,EAAE,OAAO,GAChD,UAAU,CAetB;IAED;;;;;OAKG;IACH,2DAHW;QAAC,WAAW,EAAE,OAAO,kCAAkC,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAC,GAClF;QAAC,OAAO,EAAE,MAAM,EAAE,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAC,CAelF;IAED;;;;;OAKG;IACH,0BAJW,OAAO,kCAAkC,EAAE,OAAO,eAClD,OAAO,kCAAkC,EAAE,OAAO,GAChD,OAAO,CAqBnB;IAED;;;;OAIG;IACH,sBAHW,OAAO,kCAAkC,EAAE,OAAO,GAChD,MAAM,CAIlB;IAED;;;;OAIG;IACH,iCAHW,MAAM,GACJ,MAAM,CAUlB;IAED;;;;OAIG;IACH,yBAHW,MAAM,GACJ,MAAM,CAIlB;IAED;;;;;OAKG;IACH,kCAJW,OAAO,2BAA2B,EAAE,OAAO,gBAC3C,OAAO,2BAA2B,EAAE,OAAO,GACzC,OAAO,CAOnB;IAED;;;;;;OAMG;IACH,yCAJW,OAAO,2BAA2B,EAAE,OAAO,mBAC3C;QAAC,WAAW,EAAE,OAAO,CAAA;KAAC,GACpB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAqCnC;CACF;uBAjYsB,8BAA8B"}