Line data Source code
1 : import type * as dashql from '@ankoh/dashql-core';
2 :
3 : import { ConnectorType } from '../../connections/connector_info.js';
4 : import { SNIPPET_DATASETS_FOLDER, type Snippet } from './snippet.js';
5 : import { parseSnippetFile, type SnippetFile } from './snippet_header.js';
6 :
7 : /// The snippets of the snippet browser
8 : export interface SnippetRegistry {
9 : /// The snippets, the datasets first, then ordered by path
10 : snippets: Snippet[];
11 : /// The snippets by id
12 : byId: ReadonlyMap<string, Snippet>;
13 : /// The problems of the snippet files, the affected snippets are left out
14 : errors: string[];
15 : }
16 :
17 : /// The snippet files of examples/snippets, bundled as text. The Vite config aliases the folder.
18 1 : const SNIPPET_FILES = import.meta.glob<string>('@dashql/snippets/*/*/*.sql', {
19 : query: '?raw',
20 : import: 'default',
21 : eager: true,
22 1 : });
23 :
24 : /// The text of a snippet for a connector
25 1 : export function snippetText(snippet: Snippet, connector: ConnectorType): string {
26 1 : const body = snippet.bodies.get(connector);
27 1 : if (body == null) {
28 0 : throw new Error(`snippet ${snippet.id} does not support connector ${ConnectorType[connector]}`);
29 : }
30 1 : return `${body}\n`;
31 1 : }
32 :
33 : /// Merge the files of one snippet, the engine variants of a snippet share its id
34 1 : function mergeSnippetFiles(files: SnippetFile[]): Snippet {
35 1 : const bodies = new Map<ConnectorType, string>();
36 1 : const paths = new Map<ConnectorType, string>();
37 1 : for (const file of files) {
38 1 : for (const connector of file.connectors) {
39 1 : bodies.set(connector, file.body);
40 1 : paths.set(connector, file.path);
41 1 : }
42 : }
43 1 : return {
44 1 : id: files[0].id,
45 1 : category: files[0].category,
46 1 : connectors: [...bodies.keys()],
47 1 : bodies,
48 1 : paths,
49 1 : };
50 1 : }
51 :
52 : /// Build the registry from the raw snippet files by path below the snippet folder, the folders below the engine
53 : /// folders are the categories
54 1 : export function buildSnippetRegistry(core: dashql.DashQL, rawFiles: Record<string, string>): SnippetRegistry {
55 1 : const errors: string[] = [];
56 :
57 : // Parse the files and group the engine variants of a snippet
58 1 : const filesById = new Map<string, SnippetFile[]>();
59 1 : for (const [path, raw] of Object.entries(rawFiles).sort(([l], [r]) => l.localeCompare(r))) {
60 1 : try {
61 1 : const file = parseSnippetFile(core, path, raw);
62 1 : const files = filesById.get(file.id) ?? [];
63 1 : files.push(file);
64 1 : filesById.set(file.id, files);
65 1 : } catch (error) {
66 1 : errors.push(String(error instanceof Error ? error.message : error));
67 1 : }
68 : }
69 1 : const byId = new Map([...filesById].map(([id, files]) => [id, mergeSnippetFiles(files)]));
70 1 : const datasetsFirst = (snippet: Snippet) => snippet.category === SNIPPET_DATASETS_FOLDER ? 0 : 1;
71 1 : const snippets = [...byId.values()].sort((l, r) => (datasetsFirst(l) - datasetsFirst(r)) || l.id.localeCompare(r.id));
72 1 : return { snippets, byId, errors };
73 1 : }
74 :
75 : /// The snippet files by path below the snippet folder
76 1 : export function bundledSnippetFiles(): Record<string, string> {
77 1 : const files: Record<string, string> = {};
78 1 : for (const [path, raw] of Object.entries(SNIPPET_FILES)) {
79 : // The files are `<engine>/<category>/<name>.sql`
80 1 : files[path.split('/').slice(-3).join('/')] = raw;
81 : }
82 1 : return files;
83 1 : }
84 :
85 : /// The registries of the bundled snippets by core instance
86 1 : const BUNDLED_REGISTRIES = new WeakMap<dashql.DashQL, SnippetRegistry>();
87 :
88 : /// The bundled snippets, parsed once per core instance
89 1 : export function getBundledSnippets(core: dashql.DashQL): SnippetRegistry {
90 1 : let registry = BUNDLED_REGISTRIES.get(core);
91 1 : if (registry == null) {
92 1 : registry = buildSnippetRegistry(core, bundledSnippetFiles());
93 1 : BUNDLED_REGISTRIES.set(core, registry);
94 1 : }
95 1 : return registry;
96 1 : }
97 :
98 : /// Does a snippet run on a connector?
99 1 : export function snippetSupports(snippet: Snippet, connector: ConnectorType): boolean {
100 1 : return snippet.bodies.has(connector);
101 1 : }
102 :
103 : /// The snippets that run on a connector, in the order of the registry
104 1 : export function snippetsForConnector(registry: SnippetRegistry, connector: ConnectorType): Snippet[] {
105 1 : return registry.snippets.filter(snippet => snippetSupports(snippet, connector));
106 1 : }
|