LCOV - code coverage report
Current view: top level - dashql-app/src/session/scripts - script_types.ts Coverage Total Hit
Test: DashQL 915828b Lines: 100.0 % 52 52
Test Date: 2026-11-06 19:30:32

            Line data    Source code
       1              : import type * as app_session from '@ankoh/dashql-jsonschema/app_session.js';
       2              : import type { TopLevelSpec } from 'vega-lite';
       3              : 
       4              : import type * as core_buffers from '@ankoh/dashql-core/buffers';
       5              : import { randomScriptName } from './script_name.js';
       6              : 
       7              : export interface DashboardSpec {
       8              :     row?: number | null;
       9              :     column?: number | null;
      10              :     width?: number | null;
      11              :     height?: number | null;
      12              : }
      13              : 
      14              : /// Script collection type definitions.
      15              : ///
      16              : /// These types represent the runtime state of the session's scripts.
      17              : /// Storage format types (SessionMetadata, SessionOriginType) come from JSON Schema.
      18              : 
      19              : // Re-export storage format types from JSON Schema
      20              : export type SessionMetadata = app_session.SessionMetadata;
      21              : export type SessionOriginType = NonNullable<app_session.SessionMetadata['originType']>;
      22              : 
      23              : /// A query with `@vis` tags resolved against the session. Discriminated on the
      24              : /// renderer named by the tag: `vegalite` carries a parsed Vega-Lite spec,
      25              : /// `markdown` carries the compiled row template.
      26              : /// All carry the SQL to execute against the backend and the statement with the tags.
      27              : export type ResolvedVisualizeQuery =
      28              :     | {
      29              :           renderer: 'vegalite';
      30              :           // The SQL to execute against the backend
      31              :           sql: string;
      32              :           // The statement whose @vis tags describe the visualization
      33              :           statementId: number;
      34              :           // The Vega-Lite spec generated by the analyzer
      35              :           vegaLiteSpec: TopLevelSpec;
      36              :           dashboardSpec?: DashboardSpec | null;
      37              :       }
      38              :     | {
      39              :           renderer: 'markdown';
      40              :           // The SQL to execute against the backend
      41              :           sql: string;
      42              :           // The statement whose @vis tags describe the visualization
      43              :           statementId: number;
      44              :           // The row template compiled by dashql-core.
      45              :           markdownSpec: core_buffers.visualization.MarkdownSpecT;
      46              :           dashboardSpec?: DashboardSpec | null;
      47              :       }
      48              :     | {
      49              :           renderer: 'table';
      50              :           // The SQL to execute against the backend
      51              :           sql: string;
      52              :           // The statement whose @vis tags describe the visualization
      53              :           statementId: number;
      54              :           dashboardSpec?: DashboardSpec | null;
      55              :       };
      56              : 
      57              : /// Script annotations derived from analysis
      58              : export interface ScriptAnnotations {
      59              :     // The table definitions
      60              :     tableDefs: string[];
      61              :     // The resolved visualization of this script (its @vis tags), if any.
      62              :     visualizeQuery: ResolvedVisualizeQuery | null;
      63              :     // The placement of the script on the dataflow canvas, its `@flow` tag, if any: a position, a size, or both.
      64              :     flowPosition?: FlowPosition | null;
      65              :     // The dashboard placement of the script, the `@board` tag of its last statement, if any. A script without a
      66              :     // visualization is a collapsed card on the board.
      67              :     boardPosition?: BoardPosition | null;
      68              :     // Is the last statement a query? Only a query is visualized.
      69              :     lastStatementIsQuery?: boolean;
      70              :     // Does the script run only on request, its `@manual` tag? Execute All and input changes leave it out.
      71              :     manual?: boolean;
      72              : }
      73              : 
      74              : /// The dashboard placement of a script
      75              : export interface BoardPosition extends DashboardSpec {
      76              :     // The statement whose annotation holds the `@board` tag
      77              :     statementId: number;
      78              : }
      79              : 
      80              : /// The position of a script on the dataflow canvas
      81              : export interface FlowPosition {
      82              :     // The statement whose annotation holds the `@flow` tag
      83              :     statementId: number;
      84              :     // The top left corner of the box, both null if the layout places the box
      85              :     x: number | null;
      86              :     y: number | null;
      87              :     // The width of the box, null for the default width
      88              :     width: number | null;
      89              :     // The height of the chart of the box, null for the default height
      90              :     height: number | null;
      91              : }
      92              : 
      93              : /// A reference in the session's flat ordered script collection.
      94              : export interface ScriptRef {
      95              :     // The script id
      96              :     scriptId: number;
      97              :     // The file name (e.g., "extract.sql")
      98              :     fileName: string;
      99              : }
     100              : 
     101              : /// Helper to create empty annotations
     102            1 : export function createEmptyAnnotations(): ScriptAnnotations {
     103            1 :     return {
     104            1 :         tableDefs: [],
     105            1 :         visualizeQuery: null,
     106            1 :         flowPosition: null,
     107            1 :         boardPosition: null,
     108            1 :         lastStatementIsQuery: false,
     109            1 :     };
     110            1 : }
     111              : 
     112              : /// Helper to create empty metadata
     113            1 : export function createEmptyMetadata(): SessionMetadata {
     114            1 :     return {
     115            1 :         originType: 'LOCAL',
     116            1 :         originalFileName: '',
     117            1 :         originalHttpUrl: '',
     118            1 :     };
     119            1 : }
     120              : 
     121              : /// Trailing ".sql" extension, stripped for display.
     122            1 : const SCRIPT_EXTENSION_RE = /\.sql$/i;
     123              : 
     124              : /// The display name of a script, its file name without the ".sql" extension.
     125              : /// "01_extract.sql" -> "01_extract". Used for tab/feed/details labels and to pre-fill the rename input.
     126            1 : export function scriptDisplayName(fileName: string): string {
     127            1 :     return fileName.replace(SCRIPT_EXTENSION_RE, '');
     128            1 : }
     129              : 
     130              : /// Return a script base name (no extension) that is unique among the display names of the existing
     131              : /// scripts and the other names of table parameters, e.g. those of attachments, suffixing "-2", "-3", ... on
     132              : /// collision. Names are compared in lower case, like the core finds a table parameter.
     133            1 : export function uniqueScriptBase(
     134            1 :     base: string,
     135            1 :     existingScripts: { [fileName: string]: ScriptRef },
     136            1 :     excludeFileName: string = '',
     137            1 :     takenNames: Iterable<string> = [],
     138            1 : ): string {
     139            1 :     const taken = new Set<string>();
     140            1 :     for (const key of Object.keys(existingScripts)) {
     141            1 :         if (key === excludeFileName) continue;
     142            1 :         taken.add(scriptDisplayName(key).toLowerCase());
     143            1 :     }
     144            1 :     for (const name of takenNames) taken.add(name.toLowerCase());
     145            1 :     let candidate = base;
     146            1 :     for (let suffix = 2; taken.has(candidate.toLowerCase()); ++suffix) {
     147            1 :         candidate = `${base}-${suffix}`;
     148              :     }
     149            1 :     return candidate;
     150            1 : }
     151              : 
     152              : /// Helper to generate a script file name that doesn't collide with existing entries, a random
     153              : /// "<adjective>_<animal>" name by default or the explicit `base` if given, e.g. "brave_otter.sql".
     154              : /// The order of the scripts lives in the session file, not in the file names.
     155            1 : export function generateScriptFileName(
     156            1 :     existingScripts: { [fileName: string]: ScriptRef },
     157            1 :     base?: string,
     158            1 :     takenNames: Iterable<string> = [],
     159            1 : ): string {
     160            1 :     const taken = [...takenNames];
     161            1 :     const requested = base ?? randomScriptName(new Set([...Object.keys(existingScripts).map(scriptDisplayName), ...taken]));
     162            1 :     return `${uniqueScriptBase(requested, existingScripts, '', taken)}.sql`;
     163            1 : }
     164              : 
     165              : /// Helper to create a committed script entry.
     166            1 : export function createScriptRef(scriptId: number, fileName: string): ScriptRef {
     167            1 :     return {
     168            1 :         scriptId,
     169            1 :         fileName,
     170            1 :     };
     171            1 : }
        

Generated by: LCOV version 2.0-1