@pipeline-builder/pipeline-data 0.0.1

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.
Files changed (48) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +61 -0
  3. package/drizzle.config.ts +17 -0
  4. package/lib/api/access-control-builder.d.ts +148 -0
  5. package/lib/api/access-control-builder.js +199 -0
  6. package/lib/api/crud-service.d.ts +343 -0
  7. package/lib/api/crud-service.js +849 -0
  8. package/lib/api/query-builders.d.ts +82 -0
  9. package/lib/api/query-builders.js +460 -0
  10. package/lib/api/reporting-service.d.ts +685 -0
  11. package/lib/api/reporting-service.js +1564 -0
  12. package/lib/api/soft-delete-sweep.d.ts +65 -0
  13. package/lib/api/soft-delete-sweep.js +124 -0
  14. package/lib/api/viewer-context.d.ts +29 -0
  15. package/lib/api/viewer-context.js +63 -0
  16. package/lib/core/query-filters.d.ts +263 -0
  17. package/lib/core/query-filters.js +4 -0
  18. package/lib/database/drizzle-schema.d.ts +7357 -0
  19. package/lib/database/drizzle-schema.js +66 -0
  20. package/lib/database/index.d.ts +5 -0
  21. package/lib/database/index.js +8 -0
  22. package/lib/database/migrator.d.ts +20 -0
  23. package/lib/database/migrator.js +80 -0
  24. package/lib/database/postgres-connection.d.ts +201 -0
  25. package/lib/database/postgres-connection.js +413 -0
  26. package/lib/database/retry-strategy.d.ts +68 -0
  27. package/lib/database/retry-strategy.js +122 -0
  28. package/lib/database/schema/alert.d.ts +588 -0
  29. package/lib/database/schema/alert.js +130 -0
  30. package/lib/database/schema/compliance.d.ts +3319 -0
  31. package/lib/database/schema/compliance.js +377 -0
  32. package/lib/database/schema/dashboard.d.ts +474 -0
  33. package/lib/database/schema/dashboard.js +129 -0
  34. package/lib/database/schema/message-attachment.d.ts +183 -0
  35. package/lib/database/schema/message-attachment.js +40 -0
  36. package/lib/database/schema/message.d.ts +411 -0
  37. package/lib/database/schema/message.js +114 -0
  38. package/lib/database/schema/pipeline-template.d.ts +466 -0
  39. package/lib/database/schema/pipeline-template.js +80 -0
  40. package/lib/database/schema/pipeline.d.ts +1613 -0
  41. package/lib/database/schema/pipeline.js +332 -0
  42. package/lib/database/schema/plugin.d.ts +749 -0
  43. package/lib/database/schema/plugin.js +166 -0
  44. package/lib/database/tenancy.d.ts +103 -0
  45. package/lib/database/tenancy.js +167 -0
  46. package/lib/index.d.ts +32 -0
  47. package/lib/index.js +38 -0
  48. package/package.json +161 -0
@@ -0,0 +1,474 @@
1
+ /**
2
+ * User-editable observability dashboard. Replaces the code-defined dashboards
3
+ * under `frontend/src/lib/dashboards/*.ts` so operators can add panels and
4
+ * customize layout per-org without a redeploy.
5
+ *
6
+ * `layoutJson` carries the react-grid-layout coordinate set keyed by panel id;
7
+ * `dashboard_panels` holds the panel-content rows so an N-panel dashboard
8
+ * doesn't bloat one row's JSON to several KB. The catalog query is referenced
9
+ * by key only (see platform/src/observability/catalog.ts) the dashboard
10
+ * record never carries raw PromQL/LogQL, keeping the catalog as the security
11
+ * boundary for upstream query execution.
12
+ *
13
+ * Visibility ladder * - `private` only the creator can read/write
14
+ * - `org` anyone in the same org can read; org admins + creator can write
15
+ * - `public` every authenticated user can read; only sysadmins can write.
16
+ * Used for the 5 default dashboards (`org_id='system'`) so the
17
+ * existing system-org visibility rule covers them by default.
18
+ *
19
+ * @table dashboards
20
+ */
21
+ export declare const dashboard: import("drizzle-orm/pg-core").PgTableWithColumns<{
22
+ name: "dashboards";
23
+ schema: undefined;
24
+ columns: {
25
+ id: import("drizzle-orm/pg-core").PgColumn<{
26
+ name: "id";
27
+ tableName: "dashboards";
28
+ dataType: "string";
29
+ columnType: "PgUUID";
30
+ data: string;
31
+ driverParam: string;
32
+ notNull: true;
33
+ hasDefault: true;
34
+ isPrimaryKey: true;
35
+ isAutoincrement: false;
36
+ hasRuntimeDefault: false;
37
+ enumValues: undefined;
38
+ baseColumn: never;
39
+ identity: undefined;
40
+ generated: undefined;
41
+ }, {}, {}>;
42
+ orgId: import("drizzle-orm/pg-core").PgColumn<{
43
+ name: "org_id";
44
+ tableName: "dashboards";
45
+ dataType: "string";
46
+ columnType: "PgVarchar";
47
+ data: string;
48
+ driverParam: string;
49
+ notNull: true;
50
+ hasDefault: true;
51
+ isPrimaryKey: false;
52
+ isAutoincrement: false;
53
+ hasRuntimeDefault: false;
54
+ enumValues: [string, ...string[]];
55
+ baseColumn: never;
56
+ identity: undefined;
57
+ generated: undefined;
58
+ }, {}, {
59
+ length: 255;
60
+ }>;
61
+ createdBy: import("drizzle-orm/pg-core").PgColumn<{
62
+ name: "created_by";
63
+ tableName: "dashboards";
64
+ dataType: "string";
65
+ columnType: "PgText";
66
+ data: string;
67
+ driverParam: string;
68
+ notNull: true;
69
+ hasDefault: true;
70
+ isPrimaryKey: false;
71
+ isAutoincrement: false;
72
+ hasRuntimeDefault: false;
73
+ enumValues: [string, ...string[]];
74
+ baseColumn: never;
75
+ identity: undefined;
76
+ generated: undefined;
77
+ }, {}, {}>;
78
+ createdAt: import("drizzle-orm/pg-core").PgColumn<{
79
+ name: "created_at";
80
+ tableName: "dashboards";
81
+ dataType: "date";
82
+ columnType: "PgTimestamp";
83
+ data: Date;
84
+ driverParam: string;
85
+ notNull: true;
86
+ hasDefault: true;
87
+ isPrimaryKey: false;
88
+ isAutoincrement: false;
89
+ hasRuntimeDefault: false;
90
+ enumValues: undefined;
91
+ baseColumn: never;
92
+ identity: undefined;
93
+ generated: undefined;
94
+ }, {}, {}>;
95
+ updatedBy: import("drizzle-orm/pg-core").PgColumn<{
96
+ name: "updated_by";
97
+ tableName: "dashboards";
98
+ dataType: "string";
99
+ columnType: "PgText";
100
+ data: string;
101
+ driverParam: string;
102
+ notNull: true;
103
+ hasDefault: true;
104
+ isPrimaryKey: false;
105
+ isAutoincrement: false;
106
+ hasRuntimeDefault: false;
107
+ enumValues: [string, ...string[]];
108
+ baseColumn: never;
109
+ identity: undefined;
110
+ generated: undefined;
111
+ }, {}, {}>;
112
+ updatedAt: import("drizzle-orm/pg-core").PgColumn<{
113
+ name: "updated_at";
114
+ tableName: "dashboards";
115
+ dataType: "date";
116
+ columnType: "PgTimestamp";
117
+ data: Date;
118
+ driverParam: string;
119
+ notNull: true;
120
+ hasDefault: true;
121
+ isPrimaryKey: false;
122
+ isAutoincrement: false;
123
+ hasRuntimeDefault: false;
124
+ enumValues: undefined;
125
+ baseColumn: never;
126
+ identity: undefined;
127
+ generated: undefined;
128
+ }, {}, {}>;
129
+ name: import("drizzle-orm/pg-core").PgColumn<{
130
+ name: "name";
131
+ tableName: "dashboards";
132
+ dataType: "string";
133
+ columnType: "PgVarchar";
134
+ data: string;
135
+ driverParam: string;
136
+ notNull: true;
137
+ hasDefault: false;
138
+ isPrimaryKey: false;
139
+ isAutoincrement: false;
140
+ hasRuntimeDefault: false;
141
+ enumValues: [string, ...string[]];
142
+ baseColumn: never;
143
+ identity: undefined;
144
+ generated: undefined;
145
+ }, {}, {
146
+ length: 150;
147
+ }>;
148
+ description: import("drizzle-orm/pg-core").PgColumn<{
149
+ name: "description";
150
+ tableName: "dashboards";
151
+ dataType: "string";
152
+ columnType: "PgText";
153
+ data: string;
154
+ driverParam: string;
155
+ notNull: false;
156
+ hasDefault: false;
157
+ isPrimaryKey: false;
158
+ isAutoincrement: false;
159
+ hasRuntimeDefault: false;
160
+ enumValues: [string, ...string[]];
161
+ baseColumn: never;
162
+ identity: undefined;
163
+ generated: undefined;
164
+ }, {}, {}>;
165
+ layoutJson: import("drizzle-orm/pg-core").PgColumn<{
166
+ name: "layout_json";
167
+ tableName: "dashboards";
168
+ dataType: "json";
169
+ columnType: "PgJsonb";
170
+ data: Record<string, {
171
+ x: number;
172
+ y: number;
173
+ w: number;
174
+ h: number;
175
+ minW?: number;
176
+ minH?: number;
177
+ }>;
178
+ driverParam: unknown;
179
+ notNull: true;
180
+ hasDefault: true;
181
+ isPrimaryKey: false;
182
+ isAutoincrement: false;
183
+ hasRuntimeDefault: false;
184
+ enumValues: undefined;
185
+ baseColumn: never;
186
+ identity: undefined;
187
+ generated: undefined;
188
+ }, {}, {
189
+ $type: Record<string, {
190
+ x: number;
191
+ y: number;
192
+ w: number;
193
+ h: number;
194
+ minW?: number;
195
+ minH?: number;
196
+ }>;
197
+ }>;
198
+ visibility: import("drizzle-orm/pg-core").PgColumn<{
199
+ name: "visibility";
200
+ tableName: "dashboards";
201
+ dataType: "string";
202
+ columnType: "PgVarchar";
203
+ data: "org" | "private" | "public";
204
+ driverParam: string;
205
+ notNull: true;
206
+ hasDefault: true;
207
+ isPrimaryKey: false;
208
+ isAutoincrement: false;
209
+ hasRuntimeDefault: false;
210
+ enumValues: [string, ...string[]];
211
+ baseColumn: never;
212
+ identity: undefined;
213
+ generated: undefined;
214
+ }, {}, {
215
+ length: 10;
216
+ $type: "org" | "private" | "public";
217
+ }>;
218
+ deletedAt: import("drizzle-orm/pg-core").PgColumn<{
219
+ name: "deleted_at";
220
+ tableName: "dashboards";
221
+ dataType: "date";
222
+ columnType: "PgTimestamp";
223
+ data: Date;
224
+ driverParam: string;
225
+ notNull: false;
226
+ hasDefault: false;
227
+ isPrimaryKey: false;
228
+ isAutoincrement: false;
229
+ hasRuntimeDefault: false;
230
+ enumValues: undefined;
231
+ baseColumn: never;
232
+ identity: undefined;
233
+ generated: undefined;
234
+ }, {}, {}>;
235
+ deletedBy: import("drizzle-orm/pg-core").PgColumn<{
236
+ name: "deleted_by";
237
+ tableName: "dashboards";
238
+ dataType: "string";
239
+ columnType: "PgText";
240
+ data: string;
241
+ driverParam: string;
242
+ notNull: false;
243
+ hasDefault: false;
244
+ isPrimaryKey: false;
245
+ isAutoincrement: false;
246
+ hasRuntimeDefault: false;
247
+ enumValues: [string, ...string[]];
248
+ baseColumn: never;
249
+ identity: undefined;
250
+ generated: undefined;
251
+ }, {}, {}>;
252
+ purgeAfter: import("drizzle-orm/pg-core").PgColumn<{
253
+ name: "purge_after";
254
+ tableName: "dashboards";
255
+ dataType: "date";
256
+ columnType: "PgTimestamp";
257
+ data: Date;
258
+ driverParam: string;
259
+ notNull: false;
260
+ hasDefault: false;
261
+ isPrimaryKey: false;
262
+ isAutoincrement: false;
263
+ hasRuntimeDefault: false;
264
+ enumValues: undefined;
265
+ baseColumn: never;
266
+ identity: undefined;
267
+ generated: undefined;
268
+ }, {}, {}>;
269
+ };
270
+ dialect: 'pg';
271
+ }>;
272
+ /**
273
+ * A single panel inside a dashboard. The catalog `queryKey` is the only thing
274
+ * the panel carries from the query side substitution + execution happens
275
+ * server-side via the existing catalog, so no PromQL/LogQL travels with the
276
+ * panel record.
277
+ *
278
+ * @table dashboard_panels
279
+ */
280
+ export declare const dashboardPanel: import("drizzle-orm/pg-core").PgTableWithColumns<{
281
+ name: "dashboard_panels";
282
+ schema: undefined;
283
+ columns: {
284
+ id: import("drizzle-orm/pg-core").PgColumn<{
285
+ name: "id";
286
+ tableName: "dashboard_panels";
287
+ dataType: "string";
288
+ columnType: "PgUUID";
289
+ data: string;
290
+ driverParam: string;
291
+ notNull: true;
292
+ hasDefault: true;
293
+ isPrimaryKey: true;
294
+ isAutoincrement: false;
295
+ hasRuntimeDefault: false;
296
+ enumValues: undefined;
297
+ baseColumn: never;
298
+ identity: undefined;
299
+ generated: undefined;
300
+ }, {}, {}>;
301
+ dashboardId: import("drizzle-orm/pg-core").PgColumn<{
302
+ name: "dashboard_id";
303
+ tableName: "dashboard_panels";
304
+ dataType: "string";
305
+ columnType: "PgUUID";
306
+ data: string;
307
+ driverParam: string;
308
+ notNull: true;
309
+ hasDefault: false;
310
+ isPrimaryKey: false;
311
+ isAutoincrement: false;
312
+ hasRuntimeDefault: false;
313
+ enumValues: undefined;
314
+ baseColumn: never;
315
+ identity: undefined;
316
+ generated: undefined;
317
+ }, {}, {}>;
318
+ queryKey: import("drizzle-orm/pg-core").PgColumn<{
319
+ name: "query_key";
320
+ tableName: "dashboard_panels";
321
+ dataType: "string";
322
+ columnType: "PgVarchar";
323
+ data: string;
324
+ driverParam: string;
325
+ notNull: true;
326
+ hasDefault: false;
327
+ isPrimaryKey: false;
328
+ isAutoincrement: false;
329
+ hasRuntimeDefault: false;
330
+ enumValues: [string, ...string[]];
331
+ baseColumn: never;
332
+ identity: undefined;
333
+ generated: undefined;
334
+ }, {}, {
335
+ length: 100;
336
+ }>;
337
+ vizKind: import("drizzle-orm/pg-core").PgColumn<{
338
+ name: "viz_kind";
339
+ tableName: "dashboard_panels";
340
+ dataType: "string";
341
+ columnType: "PgVarchar";
342
+ data: string;
343
+ driverParam: string;
344
+ notNull: true;
345
+ hasDefault: true;
346
+ isPrimaryKey: false;
347
+ isAutoincrement: false;
348
+ hasRuntimeDefault: false;
349
+ enumValues: [string, ...string[]];
350
+ baseColumn: never;
351
+ identity: undefined;
352
+ generated: undefined;
353
+ }, {}, {
354
+ length: 30;
355
+ }>;
356
+ title: import("drizzle-orm/pg-core").PgColumn<{
357
+ name: "title";
358
+ tableName: "dashboard_panels";
359
+ dataType: "string";
360
+ columnType: "PgVarchar";
361
+ data: string;
362
+ driverParam: string;
363
+ notNull: true;
364
+ hasDefault: false;
365
+ isPrimaryKey: false;
366
+ isAutoincrement: false;
367
+ hasRuntimeDefault: false;
368
+ enumValues: [string, ...string[]];
369
+ baseColumn: never;
370
+ identity: undefined;
371
+ generated: undefined;
372
+ }, {}, {
373
+ length: 200;
374
+ }>;
375
+ span: import("drizzle-orm/pg-core").PgColumn<{
376
+ name: "span";
377
+ tableName: "dashboard_panels";
378
+ dataType: "number";
379
+ columnType: "PgInteger";
380
+ data: number;
381
+ driverParam: string | number;
382
+ notNull: true;
383
+ hasDefault: true;
384
+ isPrimaryKey: false;
385
+ isAutoincrement: false;
386
+ hasRuntimeDefault: false;
387
+ enumValues: undefined;
388
+ baseColumn: never;
389
+ identity: undefined;
390
+ generated: undefined;
391
+ }, {}, {}>;
392
+ groupBy: import("drizzle-orm/pg-core").PgColumn<{
393
+ name: "group_by";
394
+ tableName: "dashboard_panels";
395
+ dataType: "string";
396
+ columnType: "PgVarchar";
397
+ data: string;
398
+ driverParam: string;
399
+ notNull: false;
400
+ hasDefault: false;
401
+ isPrimaryKey: false;
402
+ isAutoincrement: false;
403
+ hasRuntimeDefault: false;
404
+ enumValues: [string, ...string[]];
405
+ baseColumn: never;
406
+ identity: undefined;
407
+ generated: undefined;
408
+ }, {}, {
409
+ length: 50;
410
+ }>;
411
+ format: import("drizzle-orm/pg-core").PgColumn<{
412
+ name: "format";
413
+ tableName: "dashboard_panels";
414
+ dataType: "string";
415
+ columnType: "PgVarchar";
416
+ data: string;
417
+ driverParam: string;
418
+ notNull: false;
419
+ hasDefault: false;
420
+ isPrimaryKey: false;
421
+ isAutoincrement: false;
422
+ hasRuntimeDefault: false;
423
+ enumValues: [string, ...string[]];
424
+ baseColumn: never;
425
+ identity: undefined;
426
+ generated: undefined;
427
+ }, {}, {
428
+ length: 20;
429
+ }>;
430
+ position: import("drizzle-orm/pg-core").PgColumn<{
431
+ name: "position";
432
+ tableName: "dashboard_panels";
433
+ dataType: "number";
434
+ columnType: "PgInteger";
435
+ data: number;
436
+ driverParam: string | number;
437
+ notNull: true;
438
+ hasDefault: true;
439
+ isPrimaryKey: false;
440
+ isAutoincrement: false;
441
+ hasRuntimeDefault: false;
442
+ enumValues: undefined;
443
+ baseColumn: never;
444
+ identity: undefined;
445
+ generated: undefined;
446
+ }, {}, {}>;
447
+ vars: import("drizzle-orm/pg-core").PgColumn<{
448
+ name: "vars";
449
+ tableName: "dashboard_panels";
450
+ dataType: "json";
451
+ columnType: "PgJsonb";
452
+ data: Record<string, string>;
453
+ driverParam: unknown;
454
+ notNull: true;
455
+ hasDefault: true;
456
+ isPrimaryKey: false;
457
+ isAutoincrement: false;
458
+ hasRuntimeDefault: false;
459
+ enumValues: undefined;
460
+ baseColumn: never;
461
+ identity: undefined;
462
+ generated: undefined;
463
+ }, {}, {
464
+ $type: Record<string, string>;
465
+ }>;
466
+ };
467
+ dialect: 'pg';
468
+ }>;
469
+ export type Dashboard = typeof dashboard.$inferSelect;
470
+ export type DashboardInsert = typeof dashboard.$inferInsert;
471
+ export type DashboardUpdate = Partial<Omit<DashboardInsert, 'id' | 'createdAt' | 'createdBy'>>;
472
+ export type DashboardPanel = typeof dashboardPanel.$inferSelect;
473
+ export type DashboardPanelInsert = typeof dashboardPanel.$inferInsert;
474
+ export type DashboardPanelUpdate = Partial<Omit<DashboardPanelInsert, 'id'>>;
@@ -0,0 +1,129 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { SYSTEM_ORG_ID } from '@pipeline-builder/api-core';
4
+ import { sql } from 'drizzle-orm';
5
+ import { integer, varchar, pgTable, text, timestamp, uuid, jsonb, index, uniqueIndex } from 'drizzle-orm/pg-core';
6
+ /**
7
+ * User-editable observability dashboard. Replaces the code-defined dashboards
8
+ * under `frontend/src/lib/dashboards/*.ts` so operators can add panels and
9
+ * customize layout per-org without a redeploy.
10
+ *
11
+ * `layoutJson` carries the react-grid-layout coordinate set keyed by panel id;
12
+ * `dashboard_panels` holds the panel-content rows so an N-panel dashboard
13
+ * doesn't bloat one row's JSON to several KB. The catalog query is referenced
14
+ * by key only (see platform/src/observability/catalog.ts) the dashboard
15
+ * record never carries raw PromQL/LogQL, keeping the catalog as the security
16
+ * boundary for upstream query execution.
17
+ *
18
+ * Visibility ladder * - `private` only the creator can read/write
19
+ * - `org` anyone in the same org can read; org admins + creator can write
20
+ * - `public` every authenticated user can read; only sysadmins can write.
21
+ * Used for the 5 default dashboards (`org_id='system'`) so the
22
+ * existing system-org visibility rule covers them by default.
23
+ *
24
+ * @table dashboards
25
+ */
26
+ export const dashboard = pgTable('dashboards', {
27
+ id: uuid('id').primaryKey().defaultRandom(),
28
+ // Org + creator
29
+ orgId: varchar('org_id', { length: 255 })
30
+ .default(SYSTEM_ORG_ID)
31
+ .notNull(),
32
+ createdBy: text('created_by')
33
+ .default(SYSTEM_ORG_ID)
34
+ .notNull(),
35
+ createdAt: timestamp('created_at', { withTimezone: true })
36
+ .defaultNow()
37
+ .notNull(),
38
+ updatedBy: text('updated_by')
39
+ .default(SYSTEM_ORG_ID)
40
+ .notNull(),
41
+ updatedAt: timestamp('updated_at', { withTimezone: true })
42
+ .defaultNow()
43
+ .notNull(),
44
+ // Identity
45
+ name: varchar('name', { length: 150 })
46
+ .notNull(),
47
+ description: text('description'),
48
+ // react-grid-layout coordinate set keyed by panel id.
49
+ // Shape: { [panelId]: { x, y, w, h, minW?, minH? } }
50
+ layoutJson: jsonb('layout_json')
51
+ .$type()
52
+ .default({})
53
+ .notNull(),
54
+ // Visibility see header for ladder semantics. Constrained at the SQL
55
+ // layer too via a CHECK in postgres-init.sql to keep typos from sneaking
56
+ // past the application layer.
57
+ visibility: varchar('visibility', { length: 10 })
58
+ .$type()
59
+ .default('private')
60
+ .notNull(),
61
+ // Soft delete (matches the rest of the schema's convention)
62
+ deletedAt: timestamp('deleted_at', { withTimezone: true }),
63
+ deletedBy: text('deleted_by'),
64
+ // Soft-delete purge deadline: the retention sweep hard-deletes tombstoned
65
+ // rows once `purge_after` has passed (set on delete alongside deleted_at).
66
+ purgeAfter: timestamp('purge_after', { withTimezone: true }),
67
+ }, (table) => ({
68
+ // Partial index over just the tombstones — drives the retention purge sweep.
69
+ purgeIdx: index('dashboard_purge_idx').on(table.purgeAfter).where(sql `deleted_at IS NOT NULL`),
70
+ // Listing is org-scoped and visibility-filtered, so both go in the index.
71
+ orgVisibilityIdx: index('dashboard_org_visibility_idx').on(table.orgId, table.visibility),
72
+ // Convenience for "all my dashboards" listing
73
+ createdByIdx: index('dashboard_created_by_idx').on(table.createdBy),
74
+ // Same-org duplicate-name guard. The (orgId, name) tuple is unique among
75
+ // non-deleted rows; soft-deleted rows are excluded so name reuse after
76
+ // deletion works.
77
+ // Partial (live-rows only) to match the deployed DDL + the sibling
78
+ // orgAlertRule.orgNameUq — a tombstone must not reserve its name, so a live
79
+ // namesake can coexist (and restore can 409 on collision, handled in the route).
80
+ nameUnique: uniqueIndex('dashboard_org_name_unique').on(table.orgId, table.name).where(sql `deleted_at IS NULL`),
81
+ }));
82
+ /**
83
+ * A single panel inside a dashboard. The catalog `queryKey` is the only thing
84
+ * the panel carries from the query side substitution + execution happens
85
+ * server-side via the existing catalog, so no PromQL/LogQL travels with the
86
+ * panel record.
87
+ *
88
+ * @table dashboard_panels
89
+ */
90
+ export const dashboardPanel = pgTable('dashboard_panels', {
91
+ id: uuid('id').primaryKey().defaultRandom(),
92
+ dashboardId: uuid('dashboard_id')
93
+ .notNull(),
94
+ // Catalog key (e.g. `plugin_builds_per_min`). The server validates this
95
+ // against QUERIES at render time.
96
+ queryKey: varchar('query_key', { length: 100 })
97
+ .notNull(),
98
+ // 'stat' | 'line' | 'table' | 'stacked-bar' kept stringly-typed for
99
+ // forward compatibility; the renderer falls back to 'line' on unknown.
100
+ vizKind: varchar('viz_kind', { length: 30 })
101
+ .default('line')
102
+ .notNull(),
103
+ title: varchar('title', { length: 200 })
104
+ .notNull(),
105
+ // Tailwind col-span tier (1-12, but only 3/4/6/8/9/12 are renderable).
106
+ span: integer('span')
107
+ .default(6)
108
+ .notNull(),
109
+ // Optional: label key for series grouping (e.g. 'status', 'state').
110
+ groupBy: varchar('group_by', { length: 50 }),
111
+ // Optional: catalog format key ('percent' | 'seconds' |...).
112
+ format: varchar('format', { length: 20 }),
113
+ // 0-based render order within the dashboard. Editor reassigns on drag.
114
+ position: integer('position')
115
+ .default(0)
116
+ .notNull(),
117
+ // Optional template var values bound at panel level (e.g. `plugin=X` for
118
+ // the per-plugin drill-down panel). Sanitized server-side via the catalog
119
+ // substituteVars allow-list.
120
+ vars: jsonb('vars')
121
+ .$type()
122
+ .default({})
123
+ .notNull(),
124
+ }, (table) => ({
125
+ // The render path is "fetch all panels for dashboard X in order".
126
+ dashboardPositionIdx: index('dashboard_panel_dashboard_position_idx')
127
+ .on(table.dashboardId, table.position),
128
+ }));
129
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGFzaGJvYXJkLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL2RhdGFiYXNlL3NjaGVtYS9kYXNoYm9hcmQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsK0NBQStDO0FBQy9DLHNDQUFzQztBQUV0QyxPQUFPLEVBQUUsYUFBYSxFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFDM0QsT0FBTyxFQUFFLEdBQUcsRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUNsQyxPQUFPLEVBQUUsT0FBTyxFQUFFLE9BQU8sRUFBRSxPQUFPLEVBQUUsSUFBSSxFQUFFLFNBQVMsRUFBRSxJQUFJLEVBQUUsS0FBSyxFQUFFLEtBQUssRUFBRSxXQUFXLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQztBQUVsSDs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQW1CRztBQUNILE1BQU0sQ0FBQyxNQUFNLFNBQVMsR0FBRyxPQUFPLENBQUMsWUFBWSxFQUFFO0lBQzdDLEVBQUUsRUFBRSxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUMsVUFBVSxFQUFFLENBQUMsYUFBYSxFQUFFO0lBRTNDLGdCQUFnQjtJQUNoQixLQUFLLEVBQUUsT0FBTyxDQUFDLFFBQVEsRUFBRSxFQUFFLE1BQU0sRUFBRSxHQUFHLEVBQUUsQ0FBQztTQUN0QyxPQUFPLENBQUMsYUFBYSxDQUFDO1NBQ3RCLE9BQU8sRUFBRTtJQUNaLFNBQVMsRUFBRSxJQUFJLENBQUMsWUFBWSxDQUFDO1NBQzFCLE9BQU8sQ0FBQyxhQUFhLENBQUM7U0FDdEIsT0FBTyxFQUFFO0lBQ1osU0FBUyxFQUFFLFNBQVMsQ0FBQyxZQUFZLEVBQUUsRUFBRSxZQUFZLEVBQUUsSUFBSSxFQUFFLENBQUM7U0FDdkQsVUFBVSxFQUFFO1NBQ1osT0FBTyxFQUFFO0lBQ1osU0FBUyxFQUFFLElBQUksQ0FBQyxZQUFZLENBQUM7U0FDMUIsT0FBTyxDQUFDLGFBQWEsQ0FBQztTQUN0QixPQUFPLEVBQUU7SUFDWixTQUFTLEVBQUUsU0FBUyxD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SUFBSSxFQUFFLE9BQU8sQ0FBQyxNQUFNLENBQUM7U0FDbEIsT0FBTyxDQUFDLENBQUMsQ0FBQztTQUNWLE9BQU8sRUFBRTtJQUNaLG9FQUFvRTtJQUNwRSxPQUFPLEVBQUUsT0FBTyxDQUFDLFVBQVUsRUFBRSxFQUFFLE1BQU0sRUFBRSxFQUFFLEVBQUUsQ0FBQztJQUM1Qyw2REFBNkQ7SUFDN0QsTUFBTSxFQUFFLE9BQU8sQ0FBQyxRQUFRLEVBQUUsRUFBRSxNQUFNLEVBQUUsRUFBRSxFQUFFLENBQUM7SUFDekMsdUVBQXVFO0lBQ3ZFLFFBQVEsRUFBRSxPQUFPLENBQUMsVUFBVSxDQUFDO1NBQzFCLE9BQU8sQ0FBQyxDQUFDLENBQUM7U0FDVixPQUFPLEVBQUU7SUFDWix5RUFBeUU7SUFDekUsMEVBQTBFO0lBQzFFLDZCQUE2QjtJQUM3QixJQUFJLEVBQUUsS0FBSyxDQUFDLE1BQU0sQ0FBQztTQUNoQixLQUFLLEVBQTBCO1NBQy9CLE9BQU8sQ0FBQyxFQUFFLENBQUM7U0FDWCxPQUFPLEVBQUU7Q0FDYixFQUFFLENBQUMsS0FBSyxFQUFFLEVBQUUsQ0FBQyxDQUFDO0lBQ2Isa0VBQWtFO0lBQ2xFLG9CQUFvQixFQUFFLEtBQUssQ0FBQyx3Q0FBd0MsQ0FBQztTQUNsRSxFQUFFLENBQUMsS0FBSyxDQUFDLFdBQVcsRUFBRSxLQUFLLENBQUMsUUFBUSxDQUFDO0NBQ3pDLENBQUMsQ0FBQyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQ29weXJpZ2h0IDIwMjYgUGlwZWxpbmUgQnVpbGRlciBDb250cmlidXRvcnNcbi8vIFNQRFgtTGljZW5zZS1JZGVudGlmaWVyOiBBcGFjaGUtMi4wXG5cbmltcG9ydCB7IFNZU1RFTV9PUkdfSUQgfSBmcm9tICdAcGlwZWxpbmUtYnVpbGRlci9hcGktY29yZSc7XG5pbXBvcnQgeyBzcWwgfSBmcm9tICdkcml6emxlLW9ybSc7XG5pbXBvcnQgeyBpbnRlZ2VyLCB2YXJjaGFyLCBwZ1RhYmxlLCB0ZXh0LCB0aW1lc3RhbXAsIHV1aWQsIGpzb25iLCBpbmRleCwgdW5pcXVlSW5kZXggfSBmcm9tICdkcml6emxlLW9ybS9wZy1jb3JlJztcblxuLyoqXG4gKiBVc2VyLWVkaXRhYmxlIG9ic2VydmFiaWxpdHkgZGFzaGJvYXJkLiBSZXBsYWNlcyB0aGUgY29kZS1kZWZpbmVkIGRhc2hib2FyZHNcbiAqIHVuZGVyIGBmcm9udGVuZC9zcmMvbGliL2Rhc2hib2FyZHMvKi50c2Agc28gb3BlcmF0b3JzIGNhbiBhZGQgcGFuZWxzIGFuZFxuICogY3VzdG9taXplIGxheW91dCBwZXItb3JnIHdpdGhvdXQgYSByZWRlcGxveS5cbiAqXG4gKiBgbGF5b3V0SnNvbmAgY2FycmllcyB0aGUgcmVhY3QtZ3JpZC1sYXlvdXQgY29vcmRpbmF0ZSBzZXQga2V5ZWQgYnkgcGFuZWwgaWQ7XG4gKiBgZGFzaGJvYXJkX3BhbmVsc2AgaG9sZHMgdGhlIHBhbmVsLWNvbnRlbnQgcm93cyBzbyBhbiBOLXBhbmVsIGRhc2hib2FyZFxuICogZG9lc24ndCBibG9hdCBvbmUgcm93J3MgSlNPTiB0byBzZXZlcmFsIEtCLiBUaGUgY2F0YWxvZyBxdWVyeSBpcyByZWZlcmVuY2VkXG4gKiBieSBrZXkgb25seSAoc2VlIHBsYXRmb3JtL3NyYy9vYnNlcnZhYmlsaXR5L2NhdGFsb2cudHMpICB0aGUgZGFzaGJvYXJkXG4gKiByZWNvcmQgbmV2ZXIgY2FycmllcyByYXcgUHJvbVFML0xvZ1FMLCBrZWVwaW5nIHRoZSBjYXRhbG9nIGFzIHRoZSBzZWN1cml0eVxuICogYm91bmRhcnkgZm9yIHVwc3RyZWFtIHF1ZXJ5IGV4ZWN1dGlvbi5cbiAqXG4gKiBWaXNpYmlsaXR5IGxhZGRlciAqIC0gYHByaXZhdGVgICBvbmx5IHRoZSBjcmVhdG9yIGNhbiByZWFkL3dyaXRlXG4gKiAtIGBvcmdgICBhbnlvbmUgaW4gdGhlIHNhbWUgb3JnIGNhbiByZWFkOyBvcmcgYWRtaW5zICsgY3JlYXRvciBjYW4gd3JpdGVcbiAqIC0gYHB1YmxpY2AgIGV2ZXJ5IGF1dGhlbnRpY2F0ZWQgdXNlciBjYW4gcmVhZDsgb25seSBzeXNhZG1pbnMgY2FuIHdyaXRlLlxuICogVXNlZCBmb3IgdGhlIDUgZGVmYXVsdCBkYXNoYm9hcmRzIChgb3JnX2lkPSdzeXN0ZW0nYCkgc28gdGhlXG4gKiBleGlzdGluZyBzeXN0ZW0tb3JnIHZpc2liaWxpdHkgcnVsZSBjb3ZlcnMgdGhlbSBieSBkZWZhdWx0LlxuICpcbiAqIEB0YWJsZSBkYXNoYm9hcmRzXG4gKi9cbmV4cG9ydCBjb25zdCBkYXNoYm9hcmQgPSBwZ1RhYmxlKCdkYXNoYm9hcmRzJywge1xuICBpZDogdXVpZCgnaWQnKS5wcmltYXJ5S2V5KCkuZGVmYXVsdFJhbmRvbSgpLFxuXG4gIC8vIE9yZyArIGNyZWF0b3JcbiAgb3JnSWQ6IHZhcmNoYXIoJ29yZ19pZCcsIHsgbGVuZ3RoOiAyNTUgfSlcbiAgICAuZGVmYXVsdChTWVNURU1fT1JHX0lEKVxuICAgIC5ub3ROdWxsKCksXG4gIGNyZWF0ZWRCeTogdGV4dCgnY3JlYXRlZF9ieScpXG4gICAgLmRlZmF1bHQoU1lTVEVNX09SR19JRClcbiAgICAubm90TnVsbCgpLFxuICBjcmVhdGVkQXQ6IHRpbWVzdGFtcCgnY3JlYXRlZF9hdCcsIHsgd2l0aFRpbWV6b25lOiB0cnVlIH0pXG4gICAgLmRlZmF1bHROb3coKVxuICAgIC5ub3ROdWxsKCksXG4gIHVwZGF0ZWRCeTogdGV4dCgndXBkYXRlZF9ieScpXG4gICAgLmRlZmF1bHQoU1lTVEVNX09SR19JRClcbiAgICAubm90TnVsbCgpLFxuICB1cGRhdGVkQXQ6IHRpbWVzdGFtcCgndXBkYXRlZF9hdCcsIHsgd2l0aFRpbWV6b25lOiB0cnVlIH0pXG4gICAgLmRlZmF1bHROb3coKVxuICAgIC5ub3ROdWxsKCksXG5cbiAgLy8gSWRlbnRpdHlcbiAgbmFtZTogdmFyY2hhcignbmFtZScsIHsgbGVuZ3RoOiAxNTAgfSlcbiAgICAubm90TnVsbCgpLFxuICBkZXNjcmlwdGlvbjogdGV4dCgnZGVzY3JpcHRpb24nKSxcblxuICAvLyByZWFjdC1ncmlkLWxheW91dCBjb29yZGluYXRlIHNldCBrZXllZCBieSBwYW5lbCBpZC5cbiAgLy8gU2hhcGU6IHsgW3BhbmVsSWRdOiB7IHgsIHksIHcsIGgsIG1pblc/LCBtaW5IPyB9IH1cbiAgbGF5b3V0SnNvbjoganNvbmIoJ2xheW91dF9qc29uJylcbiAgICAuJHR5cGU8UmVjb3JkPHN0cmluZywgeyB4OiBudW1iZXI7IHk6IG51bWJlcjsgdzogbnVtYmVyOyBoOiBudW1iZXI7IG1pblc/OiBudW1iZXI7IG1pbkg/OiBudW1iZXIgfT4+KClcbiAgICAuZGVmYXVsdCh7fSlcbiAgICAubm90TnVsbCgpLFxuXG4gIC8vIFZpc2liaWxpdHkgIHNlZSBoZWFkZXIgZm9yIGxhZGRlciBzZW1hbnRpY3MuIENvbnN0cmFpbmVkIGF0IHRoZSBTUUxcbiAgLy8gbGF5ZXIgdG9vIHZpYSBhIENIRUNLIGluIHBvc3RncmVzLWluaXQuc3FsIHRvIGtlZXAgdHlwb3MgZnJvbSBzbmVha2luZ1xuICAvLyBwYXN0IHRoZSBhcHBsaWNhdGlvbiBsYXllci5cbiAgdmlzaWJpbGl0eTogdmFyY2hhcigndmlzaWJpbGl0eScsIHsgbGVuZ3RoOiAxMCB9KVxuICAgIC4kdHlwZTwncHJpdmF0ZScgfCAnb3JnJyB8ICdwdWJsaWMnPigpXG4gICAgLmRlZmF1bHQoJ3ByaXZhdGUnKVxuICAgIC5ub3ROdWxsKCksXG5cbiAgLy8gU29mdCBkZWxldGUgKG1hdGNoZXMgdGhlIHJlc3Qgb2YgdGhlIHNjaGVtYSdzIGNvbnZlbnRpb24pXG4gIGRlbGV0ZWRBdDogdGltZXN0YW1wKCdkZWxldGVkX2F0JywgeyB3aXRoVGltZXpvbmU6IHRydWUgfSksXG4gIGRlbGV0ZWRCeTogdGV4dCgnZGVsZXRlZF9ieScpLFxuICAvLyBTb2Z0LWRlbGV0ZSBwdXJnZSBkZWFkbGluZTogdGhlIHJldGVudGlvbiBzd2VlcCBoYXJkLWRlbGV0ZXMgdG9tYnN0b25lZFxuICAvLyByb3dzIG9uY2UgYHB1cmdlX2FmdGVyYCBoYXMgcGFzc2VkIChzZXQgb24gZGVsZXRlIGFsb25nc2lkZSBkZWxldGVkX2F0KS5cbiAgcHVyZ2VBZnRlcjogdGltZXN0YW1wKCdwdXJnZV9hZnRlcicsIHsgd2l0aFRpbWV6b25lOiB0cnVlIH0pLFxufSwgKHRhYmxlKSA9PiAoe1xuICAvLyBQYXJ0aWFsIGluZGV4IG92ZXIganVzdCB0aGUgdG9tYnN0b25lcyDigJQgZHJpdmVzIHRoZSByZXRlbnRpb24gcHVyZ2Ugc3dlZXAuXG4gIHB1cmdlSWR4OiBpbmRleCgnZGFzaGJvYXJkX3B1cmdlX2lkeCcpLm9uKHRhYmxlLnB1cmdlQWZ0ZXIpLndoZXJlKHNxbGBkZWxldGVkX2F0IElTIE5PVCBOVUxMYCksXG4gIC8vIExpc3RpbmcgaXMgb3JnLXNjb3BlZCBhbmQgdmlzaWJpbGl0eS1maWx0ZXJlZCwgc28gYm90aCBnbyBpbiB0aGUgaW5kZXguXG4gIG9yZ1Zpc2liaWxpdHlJZHg6IGluZGV4KCdkYXNoYm9hcmRfb3JnX3Zpc2liaWxpdHlfaWR4Jykub24odGFibGUub3JnSWQsIHRhYmxlLnZpc2liaWxpdHkpLFxuICAvLyBDb252ZW5pZW5jZSBmb3IgXCJhbGwgbXkgZGFzaGJvYXJkc1wiIGxpc3RpbmdcbiAgY3JlYXRlZEJ5SWR4OiBpbmRleCgnZGFzaGJvYXJkX2NyZWF0ZWRfYnlfaWR4Jykub24odGFibGUuY3JlYXRlZEJ5KSxcbiAgLy8gU2FtZS1vcmcgZHVwbGljYXRlLW5hbWUgZ3VhcmQuIFRoZSAob3JnSWQsIG5hbWUpIHR1cGxlIGlzIHVuaXF1ZSBhbW9uZ1xuICAvLyBub24tZGVsZXRlZCByb3dzOyBzb2Z0LWRlbGV0ZWQgcm93cyBhcmUgZXhjbHVkZWQgc28gbmFtZSByZXVzZSBhZnRlclxuICAvLyBkZWxldGlvbiB3b3Jrcy5cbiAgLy8gUGFydGlhbCAobGl2ZS1yb3dzIG9ubHkpIHRvIG1hdGNoIHRoZSBkZXBsb3llZCBEREwgKyB0aGUgc2libGluZ1xuICAvLyBvcmdBbGVydFJ1bGUub3JnTmFtZVVxIOKAlCBhIHRvbWJzdG9uZSBtdXN0IG5vdCByZXNlcnZlIGl0cyBuYW1lLCBzbyBhIGxpdmVcbiAgLy8gbmFtZXNha2UgY2FuIGNvZXhpc3QgKGFuZCByZXN0b3JlIGNhbiA0MDkgb24gY29sbGlzaW9uLCBoYW5kbGVkIGluIHRoZSByb3V0ZSkuXG4gIG5hbWVVbmlxdWU6IHVuaXF1ZUluZGV4KCdkYXNoYm9hcmRfb3JnX25hbWVfdW5pcXVlJykub24odGFibGUub3JnSWQsIHRhYmxlLm5hbWUpLndoZXJlKHNxbGBkZWxldGVkX2F0IElTIE5VTExgKSxcbn0pKTtcblxuLyoqXG4gKiBBIHNpbmdsZSBwYW5lbCBpbnNpZGUgYSBkYXNoYm9hcmQuIFRoZSBjYXRhbG9nIGBxdWVyeUtleWAgaXMgdGhlIG9ubHkgdGhpbmdcbiAqIHRoZSBwYW5lbCBjYXJyaWVzIGZyb20gdGhlIHF1ZXJ5IHNpZGUgIHN1YnN0aXR1dGlvbiArIGV4ZWN1dGlvbiBoYXBwZW5zXG4gKiBzZXJ2ZXItc2lkZSB2aWEgdGhlIGV4aXN0aW5nIGNhdGFsb2csIHNvIG5vIFByb21RTC9Mb2dRTCB0cmF2ZWxzIHdpdGggdGhlXG4gKiBwYW5lbCByZWNvcmQuXG4gKlxuICogQHRhYmxlIGRhc2hib2FyZF9wYW5lbHNcbiAqL1xuZXhwb3J0IGNvbnN0IGRhc2hib2FyZFBhbmVsID0gcGdUYWJsZSgnZGFzaGJvYXJkX3BhbmVscycsIHtcbiAgaWQ6IHV1aWQoJ2lkJykucHJpbWFyeUtleSgpLmRlZmF1bHRSYW5kb20oKSxcbiAgZGFzaGJvYXJkSWQ6IHV1aWQoJ2Rhc2hib2FyZF9pZCcpXG4gICAgLm5vdE51bGwoKSxcbiAgLy8gQ2F0YWxvZyBrZXkgKGUuZy4gYHBsdWdpbl9idWlsZHNfcGVyX21pbmApLiBUaGUgc2VydmVyIHZhbGlkYXRlcyB0aGlzXG4gIC8vIGFnYWluc3QgUVVFUklFUyBhdCByZW5kZXIgdGltZS5cbiAgcXVlcnlLZXk6IHZhcmNoYXIoJ3F1ZXJ5X2tleScsIHsgbGVuZ3RoOiAxMDAgfSlcbiAgICAubm90TnVsbCgpLFxuICAvLyAnc3RhdCcgfCAnbGluZScgfCAndGFibGUnIHwgJ3N0YWNrZWQtYmFyJyAga2VwdCBzdHJpbmdseS10eXBlZCBmb3JcbiAgLy8gZm9yd2FyZCBjb21wYXRpYmlsaXR5OyB0aGUgcmVuZGVyZXIgZmFsbHMgYmFjayB0byAnbGluZScgb24gdW5rbm93bi5cbiAgdml6S2luZDogdmFyY2hhcigndml6X2tpbmQnLCB7IGxlbmd0aDogMzAgfSlcbiAgICAuZGVmYXVsdCgnbGluZScpXG4gICAgLm5vdE51bGwoKSxcbiAgdGl0bGU6IHZhcmNoYXIoJ3RpdGxlJywgeyBsZW5ndGg6IDIwMCB9KVxuICAgIC5ub3ROdWxsKCksXG4gIC8vIFRhaWx3aW5kIGNvbC1zcGFuIHRpZXIgKDEtMTIsIGJ1dCBvbmx5IDMvNC82LzgvOS8xMiBhcmUgcmVuZGVyYWJsZSkuXG4gIHNwYW46IGludGVnZXIoJ3NwYW4nKVxuICAgIC5kZWZhdWx0KDYpXG4gICAgLm5vdE51bGwoKSxcbiAgLy8gT3B0aW9uYWw6IGxhYmVsIGtleSBmb3Igc2VyaWVzIGdyb3VwaW5nIChlLmcuICdzdGF0dXMnLCAnc3RhdGUnKS5cbiAgZ3JvdXBCeTogdmFyY2hhcignZ3JvdXBfYnknLCB7IGxlbmd0aDogNTAgfSksXG4gIC8vIE9wdGlvbmFsOiBjYXRhbG9nIGZvcm1hdCBrZXkgKCdwZXJjZW50JyB8ICdzZWNvbmRzJyB8Li4uKS5cbiAgZm9ybWF0OiB2YXJjaGFyKCdmb3JtYXQnLCB7IGxlbmd0aDogMjAgfSksXG4gIC8vIDAtYmFzZWQgcmVuZGVyIG9yZGVyIHdpdGhpbiB0aGUgZGFzaGJvYXJkLiBFZGl0b3IgcmVhc3NpZ25zIG9uIGRyYWcuXG4gIHBvc2l0aW9uOiBpbnRlZ2VyKCdwb3NpdGlvbicpXG4gICAgLmRlZmF1bHQoMClcbiAgICAubm90TnVsbCgpLFxuICAvLyBPcHRpb25hbCB0ZW1wbGF0ZSB2YXIgdmFsdWVzIGJvdW5kIGF0IHBhbmVsIGxldmVsIChlLmcuIGBwbHVnaW49WGAgZm9yXG4gIC8vIHRoZSBwZXItcGx1Z2luIGRyaWxsLWRvd24gcGFuZWwpLiBTYW5pdGl6ZWQgc2VydmVyLXNpZGUgdmlhIHRoZSBjYXRhbG9nXG4gIC8vIHN1YnN0aXR1dGVWYXJzIGFsbG93LWxpc3QuXG4gIHZhcnM6IGpzb25iKCd2YXJzJylcbiAgICAuJHR5cGU8UmVjb3JkPHN0cmluZywgc3RyaW5nPj4oKVxuICAgIC5kZWZhdWx0KHt9KVxuICAgIC5ub3ROdWxsKCksXG59LCAodGFibGUpID0+ICh7XG4gIC8vIFRoZSByZW5kZXIgcGF0aCBpcyBcImZldGNoIGFsbCBwYW5lbHMgZm9yIGRhc2hib2FyZCBYIGluIG9yZGVyXCIuXG4gIGRhc2hib2FyZFBvc2l0aW9uSWR4OiBpbmRleCgnZGFzaGJvYXJkX3BhbmVsX2Rhc2hib2FyZF9wb3NpdGlvbl9pZHgnKVxuICAgIC5vbih0YWJsZS5kYXNoYm9hcmRJZCwgdGFibGUucG9zaXRpb24pLFxufSkpO1xuXG4vLyBPYnNlcnZhYmlsaXR5IGRhc2hib2FyZHNcbmV4cG9ydCB0eXBlIERhc2hib2FyZCA9IHR5cGVvZiBkYXNoYm9hcmQuJGluZmVyU2VsZWN0O1xuZXhwb3J0IHR5cGUgRGFzaGJvYXJkSW5zZXJ0ID0gdHlwZW9mIGRhc2hib2FyZC4kaW5mZXJJbnNlcnQ7XG5leHBvcnQgdHlwZSBEYXNoYm9hcmRVcGRhdGUgPSBQYXJ0aWFsPE9taXQ8RGFzaGJvYXJkSW5zZXJ0LCAnaWQnIHwgJ2NyZWF0ZWRBdCcgfCAnY3JlYXRlZEJ5Jz4+O1xuXG5leHBvcnQgdHlwZSBEYXNoYm9hcmRQYW5lbCA9IHR5cGVvZiBkYXNoYm9hcmRQYW5lbC4kaW5mZXJTZWxlY3Q7XG5leHBvcnQgdHlwZSBEYXNoYm9hcmRQYW5lbEluc2VydCA9IHR5cGVvZiBkYXNoYm9hcmRQYW5lbC4kaW5mZXJJbnNlcnQ7XG5leHBvcnQgdHlwZSBEYXNoYm9hcmRQYW5lbFVwZGF0ZSA9IFBhcnRpYWw8T21pdDxEYXNoYm9hcmRQYW5lbEluc2VydCwgJ2lkJz4+O1xuIl19