@panoboard/core 1.11.7 → 1.12.0

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 (145) hide show
  1. package/dist/components/PanelWrapper.js +50 -5
  2. package/dist/components/PanelWrapper.js.map +1 -1
  3. package/dist/components/panels/TablePanel.d.ts +7 -1
  4. package/dist/components/panels/TablePanel.d.ts.map +1 -1
  5. package/dist/components/panels/TablePanel.js +38 -23
  6. package/dist/components/panels/TablePanel.js.map +1 -1
  7. package/dist/index.d.ts +1 -4
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +0 -8
  10. package/dist/index.js.map +1 -1
  11. package/dist/schemas/dashboard.d.ts +1 -0
  12. package/dist/schemas/dashboard.d.ts.map +1 -1
  13. package/dist/schemas/datasource.d.ts +118 -0
  14. package/dist/schemas/datasource.d.ts.map +1 -1
  15. package/dist/schemas/datasource.js +46 -0
  16. package/dist/schemas/datasource.js.map +1 -1
  17. package/dist/transforms/computed.d.ts +1 -1
  18. package/dist/transforms/computed.d.ts.map +1 -1
  19. package/dist/transforms/engine.d.ts +1 -1
  20. package/dist/transforms/engine.d.ts.map +1 -1
  21. package/dist/transforms/filter.d.ts +1 -1
  22. package/dist/transforms/filter.d.ts.map +1 -1
  23. package/dist/transforms/limit.d.ts +1 -1
  24. package/dist/transforms/limit.d.ts.map +1 -1
  25. package/dist/transforms/percentage.d.ts +1 -1
  26. package/dist/transforms/percentage.d.ts.map +1 -1
  27. package/dist/transforms/rename.d.ts +1 -1
  28. package/dist/transforms/rename.d.ts.map +1 -1
  29. package/dist/transforms/sort.d.ts +1 -1
  30. package/dist/transforms/sort.d.ts.map +1 -1
  31. package/dist/{connectors/types.d.ts → types/connector-types.d.ts} +8 -2
  32. package/dist/types/connector-types.d.ts.map +1 -0
  33. package/dist/{query/strategy/types.js → types/connector-types.js} +1 -1
  34. package/dist/types/connector-types.js.map +1 -0
  35. package/package.json +2 -39
  36. package/dist/annotations/executor.d.ts +0 -32
  37. package/dist/annotations/executor.d.ts.map +0 -1
  38. package/dist/annotations/executor.js +0 -87
  39. package/dist/annotations/executor.js.map +0 -1
  40. package/dist/annotations/index.d.ts +0 -2
  41. package/dist/annotations/index.d.ts.map +0 -1
  42. package/dist/annotations/index.js +0 -15
  43. package/dist/annotations/index.js.map +0 -1
  44. package/dist/cache/cache.d.ts +0 -11
  45. package/dist/cache/cache.d.ts.map +0 -1
  46. package/dist/cache/cache.js +0 -42
  47. package/dist/cache/cache.js.map +0 -1
  48. package/dist/cache/client.d.ts +0 -9
  49. package/dist/cache/client.d.ts.map +0 -1
  50. package/dist/cache/client.js +0 -63
  51. package/dist/cache/client.js.map +0 -1
  52. package/dist/cache/index.d.ts +0 -4
  53. package/dist/cache/index.d.ts.map +0 -1
  54. package/dist/cache/index.js +0 -17
  55. package/dist/cache/index.js.map +0 -1
  56. package/dist/cache/keys.d.ts +0 -2
  57. package/dist/cache/keys.d.ts.map +0 -1
  58. package/dist/cache/keys.js +0 -26
  59. package/dist/cache/keys.js.map +0 -1
  60. package/dist/connectors/bigquery.d.ts +0 -26
  61. package/dist/connectors/bigquery.d.ts.map +0 -1
  62. package/dist/connectors/bigquery.js +0 -118
  63. package/dist/connectors/bigquery.js.map +0 -1
  64. package/dist/connectors/index.d.ts +0 -17
  65. package/dist/connectors/index.d.ts.map +0 -1
  66. package/dist/connectors/index.js +0 -23
  67. package/dist/connectors/index.js.map +0 -1
  68. package/dist/connectors/params.d.ts +0 -15
  69. package/dist/connectors/params.d.ts.map +0 -1
  70. package/dist/connectors/params.js +0 -46
  71. package/dist/connectors/params.js.map +0 -1
  72. package/dist/connectors/postgres.d.ts +0 -29
  73. package/dist/connectors/postgres.d.ts.map +0 -1
  74. package/dist/connectors/postgres.js +0 -133
  75. package/dist/connectors/postgres.js.map +0 -1
  76. package/dist/connectors/redshift-data.d.ts +0 -25
  77. package/dist/connectors/redshift-data.d.ts.map +0 -1
  78. package/dist/connectors/redshift-data.js +0 -237
  79. package/dist/connectors/redshift-data.js.map +0 -1
  80. package/dist/connectors/redshift.d.ts +0 -29
  81. package/dist/connectors/redshift.d.ts.map +0 -1
  82. package/dist/connectors/redshift.js +0 -141
  83. package/dist/connectors/redshift.js.map +0 -1
  84. package/dist/connectors/registry.d.ts +0 -7
  85. package/dist/connectors/registry.d.ts.map +0 -1
  86. package/dist/connectors/registry.js +0 -30
  87. package/dist/connectors/registry.js.map +0 -1
  88. package/dist/connectors/snowflake.d.ts +0 -37
  89. package/dist/connectors/snowflake.d.ts.map +0 -1
  90. package/dist/connectors/snowflake.js +0 -181
  91. package/dist/connectors/snowflake.js.map +0 -1
  92. package/dist/connectors/types.d.ts.map +0 -1
  93. package/dist/connectors/types.js +0 -15
  94. package/dist/connectors/types.js.map +0 -1
  95. package/dist/connectors/url-parsers.d.ts +0 -13
  96. package/dist/connectors/url-parsers.d.ts.map +0 -1
  97. package/dist/connectors/url-parsers.js +0 -95
  98. package/dist/connectors/url-parsers.js.map +0 -1
  99. package/dist/connectors/url.d.ts +0 -36
  100. package/dist/connectors/url.d.ts.map +0 -1
  101. package/dist/connectors/url.js +0 -139
  102. package/dist/connectors/url.js.map +0 -1
  103. package/dist/query/array-expansion.d.ts +0 -5
  104. package/dist/query/array-expansion.d.ts.map +0 -1
  105. package/dist/query/array-expansion.js +0 -39
  106. package/dist/query/array-expansion.js.map +0 -1
  107. package/dist/query/index.d.ts +0 -8
  108. package/dist/query/index.d.ts.map +0 -1
  109. package/dist/query/index.js +0 -20
  110. package/dist/query/index.js.map +0 -1
  111. package/dist/query/pipeline.d.ts +0 -27
  112. package/dist/query/pipeline.d.ts.map +0 -1
  113. package/dist/query/pipeline.js +0 -49
  114. package/dist/query/pipeline.js.map +0 -1
  115. package/dist/query/strategy/http.d.ts +0 -7
  116. package/dist/query/strategy/http.d.ts.map +0 -1
  117. package/dist/query/strategy/http.js +0 -26
  118. package/dist/query/strategy/http.js.map +0 -1
  119. package/dist/query/strategy/index.d.ts +0 -4
  120. package/dist/query/strategy/index.d.ts.map +0 -1
  121. package/dist/query/strategy/index.js +0 -32
  122. package/dist/query/strategy/index.js.map +0 -1
  123. package/dist/query/strategy/sql.d.ts +0 -7
  124. package/dist/query/strategy/sql.d.ts.map +0 -1
  125. package/dist/query/strategy/sql.js +0 -40
  126. package/dist/query/strategy/sql.js.map +0 -1
  127. package/dist/query/strategy/types.d.ts +0 -6
  128. package/dist/query/strategy/types.d.ts.map +0 -1
  129. package/dist/query/strategy/types.js.map +0 -1
  130. package/dist/query/template.d.ts +0 -7
  131. package/dist/query/template.d.ts.map +0 -1
  132. package/dist/query/template.js +0 -43
  133. package/dist/query/template.js.map +0 -1
  134. package/dist/startup/index.d.ts +0 -5
  135. package/dist/startup/index.d.ts.map +0 -1
  136. package/dist/startup/index.js +0 -16
  137. package/dist/startup/index.js.map +0 -1
  138. package/dist/startup/sentinel-check.d.ts +0 -12
  139. package/dist/startup/sentinel-check.d.ts.map +0 -1
  140. package/dist/startup/sentinel-check.js +0 -142
  141. package/dist/startup/sentinel-check.js.map +0 -1
  142. package/dist/startup/sql-lint.d.ts +0 -13
  143. package/dist/startup/sql-lint.d.ts.map +0 -1
  144. package/dist/startup/sql-lint.js +0 -87
  145. package/dist/startup/sql-lint.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/query/pipeline.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EACV,SAAS,EACT,WAAW,EAGZ,MAAM,qBAAqB,CAAC;AAI7B,MAAM,WAAW,UAAU;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5E,aAAa,CAAC,EAAE,MAAM,CACpB,MAAM,EACN;QAAE,SAAS,EAAE,MAAM,EAAE,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAC9D,CAAC;IACF,OAAO,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,EAAE,CAAC;CAChE;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,UAAU,EACjB,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACvC,SAAS,EAAE,SAAS,GACnB,OAAO,CAAC,WAAW,CAAC,CAmCtB"}
@@ -1,49 +0,0 @@
1
- // Copyright 2026 Mataki Labs LLC
2
- //
3
- // Licensed under the Business Source License 1.1 (the "License");
4
- // you may not use this file except in compliance with the License.
5
- // You may obtain a copy of the License at
6
- //
7
- // https://github.com/panoboard/panoboard/blob/main/LICENSE
8
- //
9
- // Unless required by applicable law or agreed to in writing, software
10
- // distributed under the License is distributed on an "AS IS" BASIS,
11
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- // See the License for the specific language governing permissions and
13
- // limitations under the License.
14
- import { getStrategy } from "./strategy";
15
- const SORT_DIRECTION_ALLOWLIST = new Set(["ASC", "DESC", "asc", "desc"]);
16
- /**
17
- * Orchestrates query execution via the appropriate strategy:
18
- * - SQL datasources: template rendering -> array expansion -> connector.query()
19
- * - URL datasources: path + params -> connector.query()
20
- */
21
- export async function executePanelQuery(query, resolvedParams, connector) {
22
- // 0. Validate format-constrained param bindings (e.g. format: "sort_direction")
23
- for (const [paramName, binding] of Object.entries(query.params ?? {})) {
24
- if (binding.format === "sort_direction") {
25
- const value = resolvedParams[paramName];
26
- if (value !== undefined && value !== null) {
27
- if (!SORT_DIRECTION_ALLOWLIST.has(String(value))) {
28
- throw new Error(`Invalid sort_direction value "${value}" for param "${paramName}". Must be one of: ASC, DESC, asc, desc`);
29
- }
30
- }
31
- }
32
- }
33
- const strategy = getStrategy(connector.type);
34
- const request = strategy.prepare(query, resolvedParams);
35
- // For URL queries, pass panel-level columns via options so the connector can use them for parsing
36
- const options = query.columns
37
- ? {
38
- panelColumns: query.columns.map((c) => ({
39
- name: c.name,
40
- type: (c.type === "integer"
41
- ? "number"
42
- : c.type),
43
- nullable: c.nullable ?? true,
44
- })),
45
- }
46
- : undefined;
47
- return strategy.execute(connector, request, options);
48
- }
49
- //# sourceMappingURL=pipeline.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"pipeline.js","sourceRoot":"","sources":["../../src/query/pipeline.ts"],"names":[],"mappings":"AAAA,iCAAiC;AACjC,EAAE;AACF,kEAAkE;AAClE,mEAAmE;AACnE,0CAA0C;AAC1C,EAAE;AACF,+DAA+D;AAC/D,EAAE;AACF,sEAAsE;AACtE,oEAAoE;AACpE,2EAA2E;AAC3E,sEAAsE;AACtE,iCAAiC;AAEjC,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAQzC,MAAM,wBAAwB,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC;AAazE;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,KAAiB,EACjB,cAAuC,EACvC,SAAoB;IAEpB,gFAAgF;IAChF,KAAK,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC,EAAE,CAAC;QACtE,IAAI,OAAO,CAAC,MAAM,KAAK,gBAAgB,EAAE,CAAC;YACxC,MAAM,KAAK,GAAG,cAAc,CAAC,SAAS,CAAC,CAAC;YACxC,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;gBAC1C,IAAI,CAAC,wBAAwB,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;oBACjD,MAAM,IAAI,KAAK,CACb,iCAAiC,KAAK,gBAAgB,SAAS,yCAAyC,CACzG,CAAC;gBACJ,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,QAAQ,GAAG,WAAW,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IAC7C,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAC9B,KAAgC,EAChC,cAAc,CACf,CAAC;IAEF,kGAAkG;IAClG,MAAM,OAAO,GAA6B,KAAK,CAAC,OAAO;QACrD,CAAC,CAAC;YACE,YAAY,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBACtC,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS;oBACzB,CAAC,CAAC,QAAQ;oBACV,CAAC,CAAC,CAAC,CAAC,IAAI,CAAuB;gBACjC,QAAQ,EAAE,CAAC,CAAC,QAAQ,IAAI,IAAI;aAC7B,CAAC,CAAC;SACJ;QACH,CAAC,CAAC,SAAS,CAAC;IAEd,OAAO,QAAQ,CAAC,OAAO,CAAC,SAAS,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AACvD,CAAC"}
@@ -1,7 +0,0 @@
1
- import type { QueryStrategy } from "./types";
2
- import type { QueryRequest, Connector, QueryResult, QueryOptions } from "../../connectors/types";
3
- export declare class HttpStrategy implements QueryStrategy {
4
- prepare(query: Record<string, unknown>, resolvedParams: Record<string, unknown>): QueryRequest;
5
- execute(connector: Connector, request: QueryRequest, options?: QueryOptions): Promise<QueryResult>;
6
- }
7
- //# sourceMappingURL=http.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../../src/query/strategy/http.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7C,OAAO,KAAK,EACV,YAAY,EACZ,SAAS,EACT,WAAW,EACX,YAAY,EACb,MAAM,wBAAwB,CAAC;AAEhC,qBAAa,YAAa,YAAW,aAAa;IAChD,OAAO,CACL,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACtC,YAAY;IAUT,OAAO,CACX,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,YAAY,EACrB,OAAO,CAAC,EAAE,YAAY,GACrB,OAAO,CAAC,WAAW,CAAC;CAGxB"}
@@ -1,26 +0,0 @@
1
- // Copyright 2026 Mataki Labs LLC
2
- //
3
- // Licensed under the Business Source License 1.1 (the "License");
4
- // you may not use this file except in compliance with the License.
5
- // You may obtain a copy of the License at
6
- //
7
- // https://github.com/panoboard/panoboard/blob/main/LICENSE
8
- //
9
- // Unless required by applicable law or agreed to in writing, software
10
- // distributed under the License is distributed on an "AS IS" BASIS,
11
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- // See the License for the specific language governing permissions and
13
- // limitations under the License.
14
- export class HttpStrategy {
15
- prepare(query, resolvedParams) {
16
- const path = query.path;
17
- if (!path) {
18
- throw new Error("HttpStrategy requires a 'path' field in the query definition");
19
- }
20
- return { type: "http", path, params: { ...resolvedParams } };
21
- }
22
- async execute(connector, request, options) {
23
- return connector.query(request, options);
24
- }
25
- }
26
- //# sourceMappingURL=http.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"http.js","sourceRoot":"","sources":["../../../src/query/strategy/http.ts"],"names":[],"mappings":"AAAA,iCAAiC;AACjC,EAAE;AACF,kEAAkE;AAClE,mEAAmE;AACnE,0CAA0C;AAC1C,EAAE;AACF,+DAA+D;AAC/D,EAAE;AACF,sEAAsE;AACtE,oEAAoE;AACpE,2EAA2E;AAC3E,sEAAsE;AACtE,iCAAiC;AAUjC,MAAM,OAAO,YAAY;IACvB,OAAO,CACL,KAA8B,EAC9B,cAAuC;QAEvC,MAAM,IAAI,GAAG,KAAK,CAAC,IAA0B,CAAC;QAC9C,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,IAAI,KAAK,CACb,8DAA8D,CAC/D,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,cAAc,EAAE,EAAE,CAAC;IAC/D,CAAC;IAED,KAAK,CAAC,OAAO,CACX,SAAoB,EACpB,OAAqB,EACrB,OAAsB;QAEtB,OAAO,SAAS,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC3C,CAAC;CACF"}
@@ -1,4 +0,0 @@
1
- import type { DatasourceType } from "../../connectors/types";
2
- import type { QueryStrategy } from "./types";
3
- export declare function getStrategy(type: DatasourceType): QueryStrategy;
4
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/query/strategy/index.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAO7C,wBAAgB,WAAW,CAAC,IAAI,EAAE,cAAc,GAAG,aAAa,CAa/D"}
@@ -1,32 +0,0 @@
1
- // Copyright 2026 Mataki Labs LLC
2
- //
3
- // Licensed under the Business Source License 1.1 (the "License");
4
- // you may not use this file except in compliance with the License.
5
- // You may obtain a copy of the License at
6
- //
7
- // https://github.com/panoboard/panoboard/blob/main/LICENSE
8
- //
9
- // Unless required by applicable law or agreed to in writing, software
10
- // distributed under the License is distributed on an "AS IS" BASIS,
11
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- // See the License for the specific language governing permissions and
13
- // limitations under the License.
14
- import { SqlStrategy } from "./sql";
15
- import { HttpStrategy } from "./http";
16
- const sqlStrategy = new SqlStrategy();
17
- const httpStrategy = new HttpStrategy();
18
- export function getStrategy(type) {
19
- switch (type) {
20
- case "postgres":
21
- case "bigquery":
22
- case "redshift":
23
- case "redshift-data":
24
- case "snowflake":
25
- return sqlStrategy;
26
- case "url":
27
- return httpStrategy;
28
- default:
29
- throw new Error(`No strategy for datasource type: ${type}`);
30
- }
31
- }
32
- //# sourceMappingURL=index.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/query/strategy/index.ts"],"names":[],"mappings":"AAAA,iCAAiC;AACjC,EAAE;AACF,kEAAkE;AAClE,mEAAmE;AACnE,0CAA0C;AAC1C,EAAE;AACF,+DAA+D;AAC/D,EAAE;AACF,sEAAsE;AACtE,oEAAoE;AACpE,2EAA2E;AAC3E,sEAAsE;AACtE,iCAAiC;AAIjC,OAAO,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAEtC,MAAM,WAAW,GAAG,IAAI,WAAW,EAAE,CAAC;AACtC,MAAM,YAAY,GAAG,IAAI,YAAY,EAAE,CAAC;AAExC,MAAM,UAAU,WAAW,CAAC,IAAoB;IAC9C,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,UAAU,CAAC;QAChB,KAAK,UAAU,CAAC;QAChB,KAAK,UAAU,CAAC;QAChB,KAAK,eAAe,CAAC;QACrB,KAAK,WAAW;YACd,OAAO,WAAW,CAAC;QACrB,KAAK,KAAK;YACR,OAAO,YAAY,CAAC;QACtB;YACE,MAAM,IAAI,KAAK,CAAC,oCAAoC,IAAI,EAAE,CAAC,CAAC;IAChE,CAAC;AACH,CAAC"}
@@ -1,7 +0,0 @@
1
- import type { QueryStrategy } from "./types";
2
- import type { QueryRequest, Connector, QueryResult, QueryOptions } from "../../connectors/types";
3
- export declare class SqlStrategy implements QueryStrategy {
4
- prepare(query: Record<string, unknown>, resolvedParams: Record<string, unknown>): QueryRequest;
5
- execute(connector: Connector, request: QueryRequest, options?: QueryOptions): Promise<QueryResult>;
6
- }
7
- //# sourceMappingURL=sql.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"sql.d.ts","sourceRoot":"","sources":["../../../src/query/strategy/sql.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7C,OAAO,KAAK,EACV,YAAY,EACZ,SAAS,EACT,WAAW,EACX,YAAY,EACb,MAAM,wBAAwB,CAAC;AAIhC,qBAAa,WAAY,YAAW,aAAa;IAC/C,OAAO,CACL,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACtC,YAAY;IAgCT,OAAO,CACX,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,YAAY,EACrB,OAAO,CAAC,EAAE,YAAY,GACrB,OAAO,CAAC,WAAW,CAAC;CAGxB"}
@@ -1,40 +0,0 @@
1
- // Copyright 2026 Mataki Labs LLC
2
- //
3
- // Licensed under the Business Source License 1.1 (the "License");
4
- // you may not use this file except in compliance with the License.
5
- // You may obtain a copy of the License at
6
- //
7
- // https://github.com/panoboard/panoboard/blob/main/LICENSE
8
- //
9
- // Unless required by applicable law or agreed to in writing, software
10
- // distributed under the License is distributed on an "AS IS" BASIS,
11
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- // See the License for the specific language governing permissions and
13
- // limitations under the License.
14
- import { renderTemplate } from "../template";
15
- import { expandArrayParams } from "../array-expansion";
16
- export class SqlStrategy {
17
- prepare(query, resolvedParams) {
18
- const sql = query.sql;
19
- if (!sql) {
20
- throw new Error("SqlStrategy requires a 'sql' field in the query definition");
21
- }
22
- const templateVars = (query.template_vars ?? {});
23
- const resolvedVarValues = {};
24
- for (const [name, def] of Object.entries(templateVars)) {
25
- if (def.from_filter && resolvedParams[def.from_filter] !== undefined) {
26
- resolvedVarValues[name] = String(resolvedParams[def.from_filter]);
27
- }
28
- else if (def.value) {
29
- resolvedVarValues[name] = def.value;
30
- }
31
- }
32
- const renderedSql = renderTemplate(sql, templateVars, resolvedVarValues, resolvedParams);
33
- const expanded = expandArrayParams(renderedSql, resolvedParams);
34
- return { type: "sql", sql: expanded.sql, params: expanded.params };
35
- }
36
- async execute(connector, request, options) {
37
- return connector.query(request, options);
38
- }
39
- }
40
- //# sourceMappingURL=sql.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"sql.js","sourceRoot":"","sources":["../../../src/query/strategy/sql.ts"],"names":[],"mappings":"AAAA,iCAAiC;AACjC,EAAE;AACF,kEAAkE;AAClE,mEAAmE;AACnE,0CAA0C;AAC1C,EAAE;AACF,+DAA+D;AAC/D,EAAE;AACF,sEAAsE;AACtE,oEAAoE;AACpE,2EAA2E;AAC3E,sEAAsE;AACtE,iCAAiC;AASjC,OAAO,EAAE,cAAc,EAAoB,MAAM,aAAa,CAAC;AAC/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEvD,MAAM,OAAO,WAAW;IACtB,OAAO,CACL,KAA8B,EAC9B,cAAuC;QAEvC,MAAM,GAAG,GAAG,KAAK,CAAC,GAAyB,CAAC;QAC5C,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,MAAM,IAAI,KAAK,CACb,4DAA4D,CAC7D,CAAC;QACJ,CAAC;QACD,MAAM,YAAY,GAAG,CAAC,KAAK,CAAC,aAAa,IAAI,EAAE,CAG9C,CAAC;QAEF,MAAM,iBAAiB,GAA2B,EAAE,CAAC;QACrD,KAAK,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC;YACvD,IAAI,GAAG,CAAC,WAAW,IAAI,cAAc,CAAC,GAAG,CAAC,WAAW,CAAC,KAAK,SAAS,EAAE,CAAC;gBACrE,iBAAiB,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,cAAc,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC;YACpE,CAAC;iBAAM,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;gBACrB,iBAAiB,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC;YACtC,CAAC;QACH,CAAC;QAED,MAAM,WAAW,GAAG,cAAc,CAChC,GAAG,EACH,YAAY,EACZ,iBAAiB,EACjB,cAAc,CACf,CAAC;QACF,MAAM,QAAQ,GAAG,iBAAiB,CAAC,WAAW,EAAE,cAAc,CAAC,CAAC;QAEhE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC;IACrE,CAAC;IAED,KAAK,CAAC,OAAO,CACX,SAAoB,EACpB,OAAqB,EACrB,OAAsB;QAEtB,OAAO,SAAS,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC3C,CAAC;CACF"}
@@ -1,6 +0,0 @@
1
- import type { QueryRequest, Connector, QueryResult, QueryOptions } from "../../connectors/types";
2
- export interface QueryStrategy {
3
- prepare(query: Record<string, unknown>, resolvedParams: Record<string, unknown>): QueryRequest;
4
- execute(connector: Connector, request: QueryRequest, options?: QueryOptions): Promise<QueryResult>;
5
- }
6
- //# sourceMappingURL=types.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/query/strategy/types.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EACV,YAAY,EACZ,SAAS,EACT,WAAW,EACX,YAAY,EACb,MAAM,wBAAwB,CAAC;AAEhC,MAAM,WAAW,aAAa;IAC5B,OAAO,CACL,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACtC,YAAY,CAAC;IAChB,OAAO,CACL,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,YAAY,EACrB,OAAO,CAAC,EAAE,YAAY,GACrB,OAAO,CAAC,WAAW,CAAC,CAAC;CACzB"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/query/strategy/types.ts"],"names":[],"mappings":"AAAA,iCAAiC;AACjC,EAAE;AACF,kEAAkE;AAClE,mEAAmE;AACnE,0CAA0C;AAC1C,EAAE;AACF,+DAA+D;AAC/D,EAAE;AACF,sEAAsE;AACtE,oEAAoE;AACpE,2EAA2E;AAC3E,sEAAsE;AACtE,iCAAiC"}
@@ -1,7 +0,0 @@
1
- export interface TemplateVar {
2
- allowlist: string[];
3
- from_filter?: string;
4
- value?: string;
5
- }
6
- export declare function renderTemplate(sql: string, templateVars: Record<string, TemplateVar>, resolvedVarValues: Record<string, string>, params: Record<string, unknown>): string;
7
- //# sourceMappingURL=template.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"template.d.ts","sourceRoot":"","sources":["../../src/query/template.ts"],"names":[],"mappings":"AAoBA,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,cAAc,CAC5B,GAAG,EAAE,MAAM,EACX,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EACzC,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACzC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,MAAM,CAgCR"}
@@ -1,43 +0,0 @@
1
- // Copyright 2026 Mataki Labs LLC
2
- //
3
- // Licensed under the Business Source License 1.1 (the "License");
4
- // you may not use this file except in compliance with the License.
5
- // You may obtain a copy of the License at
6
- //
7
- // https://github.com/panoboard/panoboard/blob/main/LICENSE
8
- //
9
- // Unless required by applicable law or agreed to in writing, software
10
- // distributed under the License is distributed on an "AS IS" BASIS,
11
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- // See the License for the specific language governing permissions and
13
- // limitations under the License.
14
- import nunjucks from "nunjucks";
15
- const env = new nunjucks.Environment(null, { autoescape: false });
16
- const PARAMS_INTERPOLATION_RE = /\{\{-?\s*params\./;
17
- export function renderTemplate(sql, templateVars, resolvedVarValues, params) {
18
- // Guard: reject direct param interpolation — this bypasses parameterization
19
- if (PARAMS_INTERPOLATION_RE.test(sql)) {
20
- throw new Error("SQL template directly interpolates a query parameter via {{ params.X }}. " +
21
- "This bypasses parameterized queries and is a SQL injection risk. " +
22
- "Use a bound parameter (:param_name) in the SQL and declare it under query.params instead.");
23
- }
24
- // 1. Validate each template var value against its allowlist
25
- for (const [name, def] of Object.entries(templateVars)) {
26
- const value = resolvedVarValues[name];
27
- if (value === undefined && def.value) {
28
- // Use the hardcoded default value
29
- resolvedVarValues[name] = def.value;
30
- }
31
- const finalValue = resolvedVarValues[name];
32
- if (finalValue !== undefined && !def.allowlist.includes(finalValue)) {
33
- throw new Error(`Template variable '${name}' value '${finalValue}' is not in the allowlist: [${def.allowlist.join(", ")}]`);
34
- }
35
- }
36
- // 2. Render with Nunjucks -- context contains only declared template vars and params
37
- const context = {
38
- ...resolvedVarValues,
39
- params, // for {% if params.x %} conditional blocks
40
- };
41
- return env.renderString(sql, context);
42
- }
43
- //# sourceMappingURL=template.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"template.js","sourceRoot":"","sources":["../../src/query/template.ts"],"names":[],"mappings":"AAAA,iCAAiC;AACjC,EAAE;AACF,kEAAkE;AAClE,mEAAmE;AACnE,0CAA0C;AAC1C,EAAE;AACF,+DAA+D;AAC/D,EAAE;AACF,sEAAsE;AACtE,oEAAoE;AACpE,2EAA2E;AAC3E,sEAAsE;AACtE,iCAAiC;AAEjC,OAAO,QAAQ,MAAM,UAAU,CAAC;AAEhC,MAAM,GAAG,GAAG,IAAI,QAAQ,CAAC,WAAW,CAAC,IAAI,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAC;AAElE,MAAM,uBAAuB,GAAG,mBAAmB,CAAC;AAQpD,MAAM,UAAU,cAAc,CAC5B,GAAW,EACX,YAAyC,EACzC,iBAAyC,EACzC,MAA+B;IAE/B,4EAA4E;IAC5E,IAAI,uBAAuB,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CACb,2EAA2E;YACzE,mEAAmE;YACnE,2FAA2F,CAC9F,CAAC;IACJ,CAAC;IAED,4DAA4D;IAC5D,KAAK,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC;QACvD,MAAM,KAAK,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;QACtC,IAAI,KAAK,KAAK,SAAS,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;YACrC,kCAAkC;YAClC,iBAAiB,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC;QACtC,CAAC;QACD,MAAM,UAAU,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;QAC3C,IAAI,UAAU,KAAK,SAAS,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;YACpE,MAAM,IAAI,KAAK,CACb,sBAAsB,IAAI,YAAY,UAAU,+BAA+B,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAC3G,CAAC;QACJ,CAAC;IACH,CAAC;IAED,qFAAqF;IACrF,MAAM,OAAO,GAAG;QACd,GAAG,iBAAiB;QACpB,MAAM,EAAE,2CAA2C;KACpD,CAAC;IAEF,OAAO,GAAG,CAAC,YAAY,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AACxC,CAAC"}
@@ -1,5 +0,0 @@
1
- export { lintDashboardQueries } from "./sql-lint";
2
- export type { SqlLintError } from "./sql-lint";
3
- export { checkParamInterpolation } from "./sentinel-check";
4
- export type { ParamInterpolationViolation } from "./sentinel-check";
5
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/startup/index.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAClD,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAC3D,YAAY,EAAE,2BAA2B,EAAE,MAAM,kBAAkB,CAAC"}
@@ -1,16 +0,0 @@
1
- // Copyright 2026 Mataki Labs LLC
2
- //
3
- // Licensed under the Business Source License 1.1 (the "License");
4
- // you may not use this file except in compliance with the License.
5
- // You may obtain a copy of the License at
6
- //
7
- // https://github.com/panoboard/panoboard/blob/main/LICENSE
8
- //
9
- // Unless required by applicable law or agreed to in writing, software
10
- // distributed under the License is distributed on an "AS IS" BASIS,
11
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- // See the License for the specific language governing permissions and
13
- // limitations under the License.
14
- export { lintDashboardQueries } from "./sql-lint";
15
- export { checkParamInterpolation } from "./sentinel-check";
16
- //# sourceMappingURL=index.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/startup/index.ts"],"names":[],"mappings":"AAAA,iCAAiC;AACjC,EAAE;AACF,kEAAkE;AAClE,mEAAmE;AACnE,0CAA0C;AAC1C,EAAE;AACF,+DAA+D;AAC/D,EAAE;AACF,sEAAsE;AACtE,oEAAoE;AACpE,2EAA2E;AAC3E,sEAAsE;AACtE,iCAAiC;AAEjC,OAAO,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAElD,OAAO,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC"}
@@ -1,12 +0,0 @@
1
- import type { Dashboard } from "../schemas/dashboard";
2
- export interface ParamInterpolationViolation {
3
- slug: string;
4
- panelId: string;
5
- paramName: string;
6
- }
7
- /**
8
- * Check all panel SQL queries for direct param interpolation.
9
- * Returns one violation per (slug, panelId, paramName) combination found.
10
- */
11
- export declare function checkParamInterpolation(dashboards: Map<string, Dashboard>): ParamInterpolationViolation[];
12
- //# sourceMappingURL=sentinel-check.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"sentinel-check.d.ts","sourceRoot":"","sources":["../../src/startup/sentinel-check.ts"],"names":[],"mappings":"AAgCA,OAAO,KAAK,EAAE,SAAS,EAAuB,MAAM,sBAAsB,CAAC;AAI3E,MAAM,WAAW,2BAA2B;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AA8CD;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,GACjC,2BAA2B,EAAE,CA0E/B"}
@@ -1,142 +0,0 @@
1
- // Copyright 2026 Mataki Labs LLC
2
- //
3
- // Licensed under the Business Source License 1.1 (the "License");
4
- // you may not use this file except in compliance with the License.
5
- // You may obtain a copy of the License at
6
- //
7
- // https://github.com/panoboard/panoboard/blob/main/LICENSE
8
- //
9
- // Unless required by applicable law or agreed to in writing, software
10
- // distributed under the License is distributed on an "AS IS" BASIS,
11
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- // See the License for the specific language governing permissions and
13
- // limitations under the License.
14
- /**
15
- * Sentinel-based param interpolation check.
16
- *
17
- * For each panel query that has params, this check injects symbol-only sentinel
18
- * values (immune to Nunjucks string filters like | upper / | lower) and renders
19
- * the SQL template directly via Nunjucks. If any sentinel appears in the output,
20
- * the template is directly interpolating a user-supplied parameter into SQL —
21
- * a SQL injection risk.
22
- *
23
- * Symbol-only alphabet means sentinels survive filters like | upper, | lower,
24
- * | title, | capitalize, | trim without transformation.
25
- *
26
- * Note: We render via Nunjucks directly (bypassing renderTemplate) so that the
27
- * Task 1 guard — which throws on {{ params.X }} patterns — does not prevent
28
- * detection. The sentinel check IS the detection mechanism for that pattern.
29
- */
30
- import nunjucks from "nunjucks";
31
- const env = new nunjucks.Environment(null, { autoescape: false });
32
- /** Alphabet of symbols only — immune to Nunjucks case/trim filters. */
33
- const SENTINEL_ALPHABET = "@#$%^&!~";
34
- const SENTINEL_LENGTH = 16;
35
- function generateSentinel() {
36
- let s = "";
37
- for (let i = 0; i < SENTINEL_LENGTH; i++) {
38
- s += SENTINEL_ALPHABET[Math.floor(Math.random() * SENTINEL_ALPHABET.length)];
39
- }
40
- return s;
41
- }
42
- /**
43
- * Collect all panels from any dashboard layout:
44
- * - top-level panels (classic)
45
- * - section panels (classic with sections)
46
- * - tab section panels (tabbed)
47
- */
48
- function getAllPanels(dashboard) {
49
- const panels = [];
50
- for (const p of dashboard.panels ?? []) {
51
- panels.push(p);
52
- }
53
- const d = dashboard;
54
- for (const section of d.sections ?? []) {
55
- for (const p of section.panels) {
56
- panels.push(p);
57
- }
58
- }
59
- for (const tab of d.tabs ?? []) {
60
- for (const section of tab.sections) {
61
- for (const p of section.panels) {
62
- panels.push(p);
63
- }
64
- }
65
- }
66
- return panels;
67
- }
68
- /**
69
- * Check all panel SQL queries for direct param interpolation.
70
- * Returns one violation per (slug, panelId, paramName) combination found.
71
- */
72
- export function checkParamInterpolation(dashboards) {
73
- const violations = [];
74
- for (const [slug, dashboard] of dashboards) {
75
- for (const panel of getAllPanels(dashboard)) {
76
- if (!("query" in panel) || !panel.query)
77
- continue;
78
- const query = panel.query;
79
- if (!("sql" in query) || typeof query.sql !== "string")
80
- continue;
81
- if (!("params" in query) || !query.params)
82
- continue;
83
- const params = query.params;
84
- const paramNames = Object.keys(params);
85
- if (paramNames.length === 0)
86
- continue;
87
- // Build sentinel map: paramName → unique sentinel value
88
- const sentinelMap = new Map(); // paramName → sentinel
89
- const sentinelToParam = new Map(); // sentinel → paramName
90
- for (const paramName of paramNames) {
91
- let sentinel;
92
- do {
93
- sentinel = generateSentinel();
94
- } while (sentinelToParam.has(sentinel));
95
- sentinelMap.set(paramName, sentinel);
96
- sentinelToParam.set(sentinel, paramName);
97
- }
98
- // Build sentinel params object
99
- const sentinelParams = {};
100
- for (const [paramName, sentinel] of sentinelMap) {
101
- sentinelParams[paramName] = sentinel;
102
- }
103
- // Stub template_vars with first allowlist value or default
104
- const templateVars = ("template_vars" in query ? query.template_vars : undefined);
105
- const stubVarValues = {};
106
- let canRender = true;
107
- for (const [varName, varDef] of Object.entries(templateVars ?? {})) {
108
- const stubValue = varDef.allowlist[0] ?? varDef.value;
109
- if (stubValue === undefined) {
110
- canRender = false;
111
- break;
112
- }
113
- stubVarValues[varName] = stubValue;
114
- }
115
- if (!canRender)
116
- continue;
117
- // Render via Nunjucks directly — bypassing renderTemplate's guard so we
118
- // can detect the interpolation pattern ourselves via sentinel scanning.
119
- let rendered;
120
- try {
121
- rendered = env.renderString(query.sql, {
122
- ...stubVarValues,
123
- params: sentinelParams,
124
- });
125
- }
126
- catch {
127
- // Unrelated Nunjucks syntax error — skip this panel.
128
- // The runtime guard in renderTemplate or sql-lint will surface it.
129
- continue;
130
- }
131
- // Scan rendered SQL for any sentinel
132
- for (const [sentinel, paramName] of sentinelToParam) {
133
- if (rendered.includes(sentinel)) {
134
- violations.push({ slug, panelId: panel.id, paramName });
135
- break; // one violation per panel is enough
136
- }
137
- }
138
- }
139
- }
140
- return violations;
141
- }
142
- //# sourceMappingURL=sentinel-check.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"sentinel-check.js","sourceRoot":"","sources":["../../src/startup/sentinel-check.ts"],"names":[],"mappings":"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"}
@@ -1,13 +0,0 @@
1
- import type { Dashboard } from "../schemas/dashboard";
2
- export interface SqlLintError {
3
- dashboardSlug: string;
4
- panelId: string;
5
- error: string;
6
- renderedSql: string;
7
- }
8
- /**
9
- * Lint all panel queries across all dashboards.
10
- * Returns an array of errors (empty = all clear).
11
- */
12
- export declare function lintDashboardQueries(dashboards: Map<string, Dashboard>): SqlLintError[];
13
- //# sourceMappingURL=sql-lint.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"sql-lint.d.ts","sourceRoot":"","sources":["../../src/startup/sql-lint.ts"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAGtD,MAAM,WAAW,YAAY;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,GACjC,YAAY,EAAE,CAgChB"}
@@ -1,87 +0,0 @@
1
- // Copyright 2026 Mataki Labs LLC
2
- //
3
- // Licensed under the Business Source License 1.1 (the "License");
4
- // you may not use this file except in compliance with the License.
5
- // You may obtain a copy of the License at
6
- //
7
- // https://github.com/panoboard/panoboard/blob/main/LICENSE
8
- //
9
- // Unless required by applicable law or agreed to in writing, software
10
- // distributed under the License is distributed on an "AS IS" BASIS,
11
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- // See the License for the specific language governing permissions and
13
- // limitations under the License.
14
- /**
15
- * Deep SQL lint — best-effort static analysis of panel queries.
16
- *
17
- * When `startup_checks.deep_sql_lint: true`, each dashboard panel query
18
- * is rendered with the first allowlist value for each template var, then
19
- * parsed with node-sql-parser to detect syntax errors.
20
- *
21
- * This is best-effort: not all dialect-specific SQL will parse correctly.
22
- * Bind parameters (:name) are replaced with placeholder values before parsing.
23
- */
24
- import nodeSqlParser from "node-sql-parser";
25
- const { Parser } = nodeSqlParser;
26
- import { renderTemplate } from "../query/template";
27
- /**
28
- * Lint all panel queries across all dashboards.
29
- * Returns an array of errors (empty = all clear).
30
- */
31
- export function lintDashboardQueries(dashboards) {
32
- const parser = new Parser();
33
- const errors = [];
34
- for (const [slug, dashboard] of dashboards) {
35
- for (const panel of dashboard.panels ?? []) {
36
- if (!("query" in panel) || !panel.query)
37
- continue;
38
- const query = panel.query;
39
- if (!("sql" in query) || !query.sql)
40
- continue;
41
- const renderedSql = renderSqlForLint(query.sql, "template_vars" in query
42
- ? query.template_vars
43
- : undefined);
44
- const sanitized = replaceBindParams(renderedSql);
45
- try {
46
- parser.astify(sanitized, { database: "PostgreSQL" });
47
- }
48
- catch (err) {
49
- errors.push({
50
- dashboardSlug: slug,
51
- panelId: panel.id,
52
- error: err instanceof Error ? err.message : String(err),
53
- renderedSql: sanitized,
54
- });
55
- }
56
- }
57
- }
58
- return errors;
59
- }
60
- /**
61
- * Render template vars using the first allowlist value for each.
62
- */
63
- function renderSqlForLint(sql, templateVars) {
64
- if (!templateVars || Object.keys(templateVars).length === 0) {
65
- return sql;
66
- }
67
- const resolvedValues = {};
68
- for (const [name, def] of Object.entries(templateVars)) {
69
- resolvedValues[name] = def.value ?? def.allowlist[0] ?? name;
70
- }
71
- try {
72
- return renderTemplate(sql, templateVars, resolvedValues, {});
73
- }
74
- catch {
75
- // If template rendering fails, return raw SQL
76
- return sql;
77
- }
78
- }
79
- /**
80
- * Replace :bind_param placeholders with safe literal values
81
- * so the SQL parser does not choke on them.
82
- */
83
- function replaceBindParams(sql) {
84
- // Replace :word tokens (but not :: type casts) with '1'
85
- return sql.replace(/(?<!:):([a-zA-Z_]\w*)/g, "'1'");
86
- }
87
- //# sourceMappingURL=sql-lint.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"sql-lint.js","sourceRoot":"","sources":["../../src/startup/sql-lint.ts"],"names":[],"mappings":"AAAA,iCAAiC;AACjC,EAAE;AACF,kEAAkE;AAClE,mEAAmE;AACnE,0CAA0C;AAC1C,EAAE;AACF,+DAA+D;AAC/D,EAAE;AACF,sEAAsE;AACtE,oEAAoE;AACpE,2EAA2E;AAC3E,sEAAsE;AACtE,iCAAiC;AAEjC;;;;;;;;;GASG;AAEH,OAAO,aAAa,MAAM,iBAAiB,CAAC;AAC5C,MAAM,EAAE,MAAM,EAAE,GAAG,aAAa,CAAC;AAEjC,OAAO,EAAE,cAAc,EAAoB,MAAM,mBAAmB,CAAC;AASrE;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAClC,UAAkC;IAElC,MAAM,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;IAC5B,MAAM,MAAM,GAAmB,EAAE,CAAC;IAElC,KAAK,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,IAAI,UAAU,EAAE,CAAC;QAC3C,KAAK,MAAM,KAAK,IAAI,SAAS,CAAC,MAAM,IAAI,EAAE,EAAE,CAAC;YAC3C,IAAI,CAAC,CAAC,OAAO,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK;gBAAE,SAAS;YAClD,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;YAC1B,IAAI,CAAC,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG;gBAAE,SAAS;YAE9C,MAAM,WAAW,GAAG,gBAAgB,CAClC,KAAK,CAAC,GAAG,EACT,eAAe,IAAI,KAAK;gBACtB,CAAC,CAAE,KAAK,CAAC,aAAyD;gBAClE,CAAC,CAAC,SAAS,CACd,CAAC;YACF,MAAM,SAAS,GAAG,iBAAiB,CAAC,WAAW,CAAC,CAAC;YAEjD,IAAI,CAAC;gBACH,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC,CAAC;YACvD,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,CAAC,IAAI,CAAC;oBACV,aAAa,EAAE,IAAI;oBACnB,OAAO,EAAE,KAAK,CAAC,EAAE;oBACjB,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;oBACvD,WAAW,EAAE,SAAS;iBACvB,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,SAAS,gBAAgB,CACvB,GAAW,EACX,YAA0C;IAE1C,IAAI,CAAC,YAAY,IAAI,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5D,OAAO,GAAG,CAAC;IACb,CAAC;IAED,MAAM,cAAc,GAA2B,EAAE,CAAC;IAClD,KAAK,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC;QACvD,cAAc,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;IAC/D,CAAC;IAED,IAAI,CAAC;QACH,OAAO,cAAc,CAAC,GAAG,EAAE,YAAY,EAAE,cAAc,EAAE,EAAE,CAAC,CAAC;IAC/D,CAAC;IAAC,MAAM,CAAC;QACP,8CAA8C;QAC9C,OAAO,GAAG,CAAC;IACb,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,SAAS,iBAAiB,CAAC,GAAW;IACpC,wDAAwD;IACxD,OAAO,GAAG,CAAC,OAAO,CAAC,wBAAwB,EAAE,KAAK,CAAC,CAAC;AACtD,CAAC"}