@graphenedata/cli 0.0.4 → 0.0.6
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.
- package/cli.ts +25 -55
- package/dist/cli/cli.js +1194 -435
- package/dist/docs/graphene.md +1074 -166
- package/dist/ui/component-utilities/echarts.js +3 -1
- package/dist/ui/component-utilities/inputUtils.ts +11 -0
- package/dist/ui/component-utilities/themeStores.ts +35 -7
- package/dist/ui/components/Area.svelte +6 -3
- package/dist/ui/components/AreaChart.svelte +3 -1
- package/dist/ui/components/Bar.svelte +14 -8
- package/dist/ui/components/BarChart.svelte +3 -1
- package/dist/ui/components/BigValue.svelte +1 -1
- package/dist/ui/components/Chart.svelte +57 -101
- package/dist/ui/components/Column.svelte +2 -0
- package/dist/ui/components/ECharts.svelte +2 -0
- package/dist/ui/components/Line.svelte +8 -5
- package/dist/ui/components/LineChart.svelte +3 -2
- package/dist/ui/components/PieChart.svelte +1 -1
- package/dist/ui/components/QueryLoad.svelte +5 -6
- package/dist/ui/components/TableRow.svelte +1 -1
- package/dist/ui/components/_Table.svelte +2 -0
- package/dist/ui/internal/queryEngine.ts +39 -15
- package/dist/ui/internal/telemetry.ts +5 -3
- package/dist/ui/web.js +28 -12
- package/package.json +3 -2
- package/dist/docs/data_apps/components/charts/annotations.md +0 -673
- package/dist/docs/data_apps/components/charts/area-chart.md +0 -202
- package/dist/docs/data_apps/components/charts/bar-chart.md +0 -317
- package/dist/docs/data_apps/components/charts/box-plot.md +0 -190
- package/dist/docs/data_apps/components/charts/bubble-chart.md +0 -151
- package/dist/docs/data_apps/components/charts/calendar-heatmap.md +0 -112
- package/dist/docs/data_apps/components/charts/custom-echarts.md +0 -308
- package/dist/docs/data_apps/components/charts/echarts-options.md +0 -217
- package/dist/docs/data_apps/components/charts/funnel-chart.md +0 -106
- package/dist/docs/data_apps/components/charts/heatmap.md +0 -180
- package/dist/docs/data_apps/components/charts/histogram.md +0 -107
- package/dist/docs/data_apps/components/charts/line-chart.md +0 -265
- package/dist/docs/data_apps/components/charts/mixed-type-charts.md +0 -240
- package/dist/docs/data_apps/components/charts/sankey-diagram.md +0 -301
- package/dist/docs/data_apps/components/charts/scatter-plot.md +0 -134
- package/dist/docs/data_apps/components/charts/sparkline.md +0 -68
- package/dist/docs/data_apps/components/data/big-value.md +0 -153
- package/dist/docs/data_apps/components/data/delta.md +0 -89
- package/dist/docs/data_apps/components/data/table.md +0 -470
- package/dist/docs/data_apps/components/data/value.md +0 -97
- package/dist/docs/data_apps/components/inputs/button-group.md +0 -154
- package/dist/docs/data_apps/components/inputs/checkbox.md +0 -52
- package/dist/docs/data_apps/components/inputs/date-input.md +0 -131
- package/dist/docs/data_apps/components/inputs/date-range.md +0 -124
- package/dist/docs/data_apps/components/inputs/dimension-grid.md +0 -67
- package/dist/docs/data_apps/components/inputs/dropdown.md +0 -199
- package/dist/docs/data_apps/components/inputs/index.md +0 -3
- package/dist/docs/data_apps/components/inputs/slider.md +0 -126
- package/dist/docs/data_apps/components/inputs/text-input.md +0 -86
- package/dist/docs/data_apps/components/maps/area-map.md +0 -397
- package/dist/docs/data_apps/components/maps/base-map.md +0 -269
- package/dist/docs/data_apps/components/maps/bubble-map.md +0 -361
- package/dist/docs/data_apps/components/maps/point-map.md +0 -326
- package/dist/docs/data_apps/components/maps/us-map.md +0 -167
- package/dist/docs/data_apps/components/ui/accordion.md +0 -116
- package/dist/docs/data_apps/components/ui/alert.md +0 -37
- package/dist/docs/data_apps/components/ui/big-link.md +0 -19
- package/dist/docs/data_apps/components/ui/details.md +0 -58
- package/dist/docs/data_apps/components/ui/download-data.md +0 -41
- package/dist/docs/data_apps/components/ui/embed.md +0 -47
- package/dist/docs/data_apps/components/ui/grid.md +0 -45
- package/dist/docs/data_apps/components/ui/image.md +0 -61
- package/dist/docs/data_apps/components/ui/info.md +0 -47
- package/dist/docs/data_apps/components/ui/last-refreshed.md +0 -28
- package/dist/docs/data_apps/components/ui/link-button.md +0 -20
- package/dist/docs/data_apps/components/ui/link.md +0 -40
- package/dist/docs/data_apps/components/ui/modal.md +0 -57
- package/dist/docs/data_apps/components/ui/note.md +0 -32
- package/dist/docs/data_apps/components/ui/print-format-components.md +0 -85
- package/dist/docs/data_apps/components/ui/tabs.md +0 -122
package/dist/cli/cli.js
CHANGED
|
@@ -58,8 +58,18 @@ function walkExpression(root, fn, parent = null) {
|
|
|
58
58
|
});
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
|
+
async function pollFor(fn, timeoutMs, interval) {
|
|
62
|
+
let end = Date.now() + timeoutMs;
|
|
63
|
+
while (Date.now() < end) {
|
|
64
|
+
let res = fn();
|
|
65
|
+
if (res) return res;
|
|
66
|
+
await new Promise((r) => setTimeout(r, interval));
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
61
70
|
var init_util = __esm({
|
|
62
71
|
"../lang/util.ts"() {
|
|
72
|
+
"use strict";
|
|
63
73
|
}
|
|
64
74
|
});
|
|
65
75
|
|
|
@@ -126,6 +136,7 @@ function extractLeadingMetadata(node) {
|
|
|
126
136
|
}
|
|
127
137
|
var init_metadata = __esm({
|
|
128
138
|
"../lang/metadata.ts"() {
|
|
139
|
+
"use strict";
|
|
129
140
|
init_util();
|
|
130
141
|
}
|
|
131
142
|
});
|
|
@@ -134,8 +145,12 @@ var init_metadata = __esm({
|
|
|
134
145
|
import * as fs from "fs";
|
|
135
146
|
import path from "path";
|
|
136
147
|
function setConfig(cfg) {
|
|
148
|
+
let dialect = cfg.dialect || "duckdb";
|
|
149
|
+
if (cfg.bigquery) dialect = "bigquery";
|
|
150
|
+
else if (cfg.snowflake) dialect = "snowflake";
|
|
151
|
+
else if (cfg.duckdb) dialect = "duckdb";
|
|
137
152
|
Object.keys(config).forEach((key) => delete config[key]);
|
|
138
|
-
Object.assign(config, cfg);
|
|
153
|
+
Object.assign(config, cfg, { dialect });
|
|
139
154
|
}
|
|
140
155
|
function loadConfig(dir) {
|
|
141
156
|
if (config.root) return;
|
|
@@ -147,17 +162,13 @@ function loadConfig(dir) {
|
|
|
147
162
|
} catch {
|
|
148
163
|
console.warn("No package.json found in current directory");
|
|
149
164
|
}
|
|
150
|
-
setConfig({
|
|
151
|
-
...packageJsonObject,
|
|
152
|
-
dialect: packageJsonObject.dialect || "duckdb",
|
|
153
|
-
port: process.env.GRAPHENE_PORT || packageJsonObject.port || 4e3,
|
|
154
|
-
root: packageJsonObject.root || process.cwd()
|
|
155
|
-
});
|
|
165
|
+
setConfig({ ...packageJsonObject, root: packageJsonObject.root || process.cwd() });
|
|
156
166
|
}
|
|
157
167
|
var config;
|
|
158
168
|
var init_config = __esm({
|
|
159
169
|
"../lang/config.ts"() {
|
|
160
|
-
|
|
170
|
+
"use strict";
|
|
171
|
+
config = { dialect: "duckdb", root: "" };
|
|
161
172
|
}
|
|
162
173
|
});
|
|
163
174
|
|
|
@@ -174,6 +185,7 @@ function findOverloads(name, dialect) {
|
|
|
174
185
|
var globalNamespace, dialectNamespaces, BIGQUERY_DIALECT_FUNCTIONS;
|
|
175
186
|
var init_functions = __esm({
|
|
176
187
|
"../lang/functions.ts"() {
|
|
188
|
+
"use strict";
|
|
177
189
|
globalNamespace = new GlobalNameSpace();
|
|
178
190
|
dialectNamespaces = /* @__PURE__ */ new Map();
|
|
179
191
|
Object.assign(DUCKDB_DIALECT_FUNCTIONS, {
|
|
@@ -303,6 +315,138 @@ var init_functions = __esm({
|
|
|
303
315
|
}
|
|
304
316
|
});
|
|
305
317
|
|
|
318
|
+
// ../lang/temporalLiterals.ts
|
|
319
|
+
function parseTemporalLiteral(value, expected) {
|
|
320
|
+
let raw = (value ?? "").trim();
|
|
321
|
+
if (!raw) return null;
|
|
322
|
+
let yearMatch = raw.match(/^([0-9]{4})$/);
|
|
323
|
+
if (yearMatch) {
|
|
324
|
+
let year = Number(yearMatch[1]);
|
|
325
|
+
return buildResult(year, 1, 1, 0, 0, 0, "year", expected);
|
|
326
|
+
}
|
|
327
|
+
let yearMonthMatch = raw.match(/^([0-9]{4})-([0-9]{2})$/);
|
|
328
|
+
if (yearMonthMatch) {
|
|
329
|
+
let year = Number(yearMonthMatch[1]);
|
|
330
|
+
let month = Number(yearMonthMatch[2]);
|
|
331
|
+
if (!inRange(month, 1, 12)) return null;
|
|
332
|
+
return buildResult(year, month, 1, 0, 0, 0, "month", expected);
|
|
333
|
+
}
|
|
334
|
+
let dateMatch = raw.match(/^([0-9]{4})-([0-9]{2})-([0-9]{2})$/);
|
|
335
|
+
if (dateMatch) {
|
|
336
|
+
let year = Number(dateMatch[1]);
|
|
337
|
+
let month = Number(dateMatch[2]);
|
|
338
|
+
let day = Number(dateMatch[3]);
|
|
339
|
+
if (!isValidDate(year, month, day)) return null;
|
|
340
|
+
return buildResult(year, month, day, 0, 0, 0, "day", expected);
|
|
341
|
+
}
|
|
342
|
+
if (expected === "timestamp") {
|
|
343
|
+
let dateTimeMatch = raw.match(/^([0-9]{4})-([0-9]{2})-([0-9]{2})[Tt\s]([0-9]{1,2})(?::([0-9]{2})(?::([0-9]{2}))?)?$/);
|
|
344
|
+
if (!dateTimeMatch) return null;
|
|
345
|
+
let year = Number(dateTimeMatch[1]);
|
|
346
|
+
let month = Number(dateTimeMatch[2]);
|
|
347
|
+
let day = Number(dateTimeMatch[3]);
|
|
348
|
+
if (!isValidDate(year, month, day)) return null;
|
|
349
|
+
let hour = Number(dateTimeMatch[4]);
|
|
350
|
+
let minute = dateTimeMatch[5] ? Number(dateTimeMatch[5]) : 0;
|
|
351
|
+
let second = dateTimeMatch[6] ? Number(dateTimeMatch[6]) : 0;
|
|
352
|
+
if (!inRange(hour, 0, 23) || !inRange(minute, 0, 59) || !inRange(second, 0, 59)) return null;
|
|
353
|
+
let timeframe = "hour";
|
|
354
|
+
if (dateTimeMatch[6]) {
|
|
355
|
+
timeframe = "second";
|
|
356
|
+
} else if (dateTimeMatch[5]) {
|
|
357
|
+
timeframe = "minute";
|
|
358
|
+
}
|
|
359
|
+
return buildResult(year, month, day, hour, minute, second, timeframe, expected);
|
|
360
|
+
}
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
function parseTemporal(node) {
|
|
364
|
+
if (node.node !== "stringLiteral") return null;
|
|
365
|
+
let rawValue = typeof node.literal === "string" ? node.literal.trim() : "";
|
|
366
|
+
if (!rawValue) return null;
|
|
367
|
+
let parsedDate = parseTemporalLiteral(rawValue, "date");
|
|
368
|
+
if (parsedDate) {
|
|
369
|
+
let typeDef = { type: parsedDate.type, timeframe: parsedDate.timeframe };
|
|
370
|
+
Object.assign(node, { node: "timeLiteral", literal: parsedDate.literal, type: parsedDate.type, typeDef });
|
|
371
|
+
return parsedDate.timeframe;
|
|
372
|
+
}
|
|
373
|
+
let parsedTimestamp = parseTemporalLiteral(rawValue, "timestamp");
|
|
374
|
+
if (parsedTimestamp) {
|
|
375
|
+
let typeDef = { type: parsedTimestamp.type, timeframe: parsedTimestamp.timeframe };
|
|
376
|
+
Object.assign(node, { node: "timeLiteral", literal: parsedTimestamp.literal, type: parsedTimestamp.type, typeDef });
|
|
377
|
+
return parsedTimestamp.timeframe;
|
|
378
|
+
}
|
|
379
|
+
let interval = parseIntervalLiteral(rawValue);
|
|
380
|
+
if (interval) {
|
|
381
|
+
Object.assign(node, { node: "numberLiteral", literal: interval.quantity.toString(), type: "interval", intervalUnit: interval.unit });
|
|
382
|
+
return interval.unit;
|
|
383
|
+
}
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
function parseIntervalLiteral(value) {
|
|
387
|
+
let raw = (value ?? "").trim().toLowerCase().replace(/\s+/g, " ");
|
|
388
|
+
if (!raw) return null;
|
|
389
|
+
let match = raw.match(/^(-?\d+(?:\.\d+)?)\s+([a-z]+)$/);
|
|
390
|
+
if (!match) return null;
|
|
391
|
+
let quantity = Number(match[1]);
|
|
392
|
+
if (!Number.isFinite(quantity)) return null;
|
|
393
|
+
let unit = INTERVAL_UNITS[match[2]];
|
|
394
|
+
if (!unit) return null;
|
|
395
|
+
return { quantity, unit };
|
|
396
|
+
}
|
|
397
|
+
function buildResult(year, month, day, hour, minute, second, timeframe, expected) {
|
|
398
|
+
if (expected === "date") {
|
|
399
|
+
return { literal: `${pad(year)}-${pad(month)}-${pad(day)}`, timeframe, type: "date" };
|
|
400
|
+
}
|
|
401
|
+
return {
|
|
402
|
+
literal: `${pad(year)}-${pad(month)}-${pad(day)} ${pad(hour)}:${pad(minute)}:${pad(second)}`,
|
|
403
|
+
timeframe,
|
|
404
|
+
type: "timestamp"
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
function inRange(value, min, max) {
|
|
408
|
+
return Number.isInteger(value) && value >= min && value <= max;
|
|
409
|
+
}
|
|
410
|
+
function isValidDate(year, month, day) {
|
|
411
|
+
if (!inRange(month, 1, 12)) return false;
|
|
412
|
+
if (!inRange(day, 1, 31)) return false;
|
|
413
|
+
let date = new Date(Date.UTC(year, month - 1, day));
|
|
414
|
+
return date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 && date.getUTCDate() === day;
|
|
415
|
+
}
|
|
416
|
+
function pad(value) {
|
|
417
|
+
return value.toString().padStart(2, "0");
|
|
418
|
+
}
|
|
419
|
+
var INTERVAL_UNITS;
|
|
420
|
+
var init_temporalLiterals = __esm({
|
|
421
|
+
"../lang/temporalLiterals.ts"() {
|
|
422
|
+
"use strict";
|
|
423
|
+
INTERVAL_UNITS = {
|
|
424
|
+
second: "second",
|
|
425
|
+
seconds: "second",
|
|
426
|
+
sec: "second",
|
|
427
|
+
secs: "second",
|
|
428
|
+
minute: "minute",
|
|
429
|
+
minutes: "minute",
|
|
430
|
+
min: "minute",
|
|
431
|
+
mins: "minute",
|
|
432
|
+
hour: "hour",
|
|
433
|
+
hours: "hour",
|
|
434
|
+
hr: "hour",
|
|
435
|
+
hrs: "hour",
|
|
436
|
+
day: "day",
|
|
437
|
+
days: "day",
|
|
438
|
+
week: "week",
|
|
439
|
+
weeks: "week",
|
|
440
|
+
month: "month",
|
|
441
|
+
months: "month",
|
|
442
|
+
quarter: "quarter",
|
|
443
|
+
quarters: "quarter",
|
|
444
|
+
year: "year",
|
|
445
|
+
years: "year"
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
|
|
306
450
|
// ../lang/params.ts
|
|
307
451
|
function inferParamTypes(query) {
|
|
308
452
|
let parentMap = /* @__PURE__ */ new WeakMap();
|
|
@@ -409,8 +553,7 @@ function sanitizeType(value) {
|
|
|
409
553
|
return value;
|
|
410
554
|
}
|
|
411
555
|
function fillInParams(query, params) {
|
|
412
|
-
let
|
|
413
|
-
let filters = q.pipeline[0].filterList || [];
|
|
556
|
+
let filters = query.pipeline[0].filterList || [];
|
|
414
557
|
for (let filter of filters) {
|
|
415
558
|
walkExpression(filter.e, (e) => {
|
|
416
559
|
if (e.node !== "parameter") return;
|
|
@@ -419,20 +562,31 @@ function fillInParams(query, params) {
|
|
|
419
562
|
else if (value == null) Object.assign(e, { node: "null", type: "string" });
|
|
420
563
|
else if (e.type == "string") Object.assign(e, { node: "stringLiteral", literal: value });
|
|
421
564
|
else if (e.type == "number") Object.assign(e, { node: "numberLiteral", literal: value.toString() });
|
|
422
|
-
else if (e.type == "
|
|
565
|
+
else if (e.type == "date" || e.type == "timestamp") {
|
|
566
|
+
if (typeof value !== "string") throw new Error(`Parameters of type ${e.type} must be provided as strings`);
|
|
567
|
+
let parsed = parseTemporalLiteral(value, e.type);
|
|
568
|
+
if (!parsed) throw new Error(`Could not parse ${e.type} literal for param $${e.path[0]}`);
|
|
569
|
+
Object.assign(e, { node: "timeLiteral", literal: parsed.literal, type: parsed.type, typeDef: { type: parsed.type, timeframe: parsed.timeframe } });
|
|
570
|
+
} else if (e.type == "interval") {
|
|
571
|
+
if (typeof value !== "string") throw new Error("Parameters of type interval must be provided as strings");
|
|
572
|
+
let parsed = parseIntervalLiteral(value);
|
|
573
|
+
if (!parsed) throw new Error(`Could not parse interval literal for param $${e.path[0]}`);
|
|
574
|
+
Object.assign(e, { node: "numberLiteral", literal: parsed.quantity.toString(), type: "interval", intervalUnit: parsed.unit });
|
|
575
|
+
} else if (e.type == "boolean") Object.assign(e, { node: value ? "true" : "false" });
|
|
423
576
|
else throw new Error(`Unsupported param type ${e.type}`);
|
|
424
577
|
});
|
|
425
578
|
}
|
|
426
|
-
return q;
|
|
427
579
|
}
|
|
428
580
|
var COMPARISON_OPS, BOOLEAN_OPS, NUMERIC_UNARY_OPS, FIELD_TYPES;
|
|
429
581
|
var init_params = __esm({
|
|
430
582
|
"../lang/params.ts"() {
|
|
583
|
+
"use strict";
|
|
431
584
|
init_util();
|
|
585
|
+
init_temporalLiterals();
|
|
432
586
|
COMPARISON_OPS = /* @__PURE__ */ new Set(["=", "!=", "<>", ">", ">=", "<", "<=", "like", "ilike"]);
|
|
433
587
|
BOOLEAN_OPS = /* @__PURE__ */ new Set(["and", "or"]);
|
|
434
588
|
NUMERIC_UNARY_OPS = /* @__PURE__ */ new Set(["unary-"]);
|
|
435
|
-
FIELD_TYPES = /* @__PURE__ */ new Set(["string", "number", "boolean", "date", "timestamp", "json", "sql native", "error", "fieldref", "array", "record", "null"]);
|
|
589
|
+
FIELD_TYPES = /* @__PURE__ */ new Set(["string", "number", "boolean", "date", "timestamp", "json", "sql native", "error", "fieldref", "array", "record", "null", "interval"]);
|
|
436
590
|
}
|
|
437
591
|
});
|
|
438
592
|
|
|
@@ -450,34 +604,9 @@ function findTables(fi) {
|
|
|
450
604
|
}
|
|
451
605
|
let table2 = makeTable(name, syntaxNode.getChild("QueryStatement") ? "query_source" : "table");
|
|
452
606
|
table2.metadata = extractLeadingMetadata(syntaxNode);
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
if (table2.primaryKey) diag(cn, `Table ${table2.name} has multiple primary keys`);
|
|
457
|
-
table2.primaryKey = name2;
|
|
458
|
-
}
|
|
459
|
-
let type = convertDataType(txt(cn.getChild("DataType")));
|
|
460
|
-
if (!type) diag(cn, `Unsupported data type: ${txt(cn.getChild("DataType"))}`);
|
|
461
|
-
let field = { name: name2, type, metadata: extractLeadingMetadata(cn) };
|
|
462
|
-
table2.fields.push(field);
|
|
463
|
-
FIELD_NODE_MAP.set(field, cn);
|
|
464
|
-
}
|
|
465
|
-
for (let jn of syntaxNode.getChildren("JoinDef")) {
|
|
466
|
-
let nameNode = jn.getChild("Alias") || jn.getChild("Identifier");
|
|
467
|
-
let field = { name: txt(nameNode) };
|
|
468
|
-
table2.fields.push(field);
|
|
469
|
-
FIELD_NODE_MAP.set(field, jn);
|
|
470
|
-
}
|
|
471
|
-
for (let cn of syntaxNode.getChildren("ComputedDef")) {
|
|
472
|
-
let field = { name: txt(cn.getChild("Alias")), metadata: extractLeadingMetadata(cn) };
|
|
473
|
-
table2.fields.push(field);
|
|
474
|
-
FIELD_NODE_MAP.set(field, cn);
|
|
475
|
-
}
|
|
476
|
-
table2.fields.reduce((set, f) => {
|
|
477
|
-
if (!set[f.name]) set[f.name] = true;
|
|
478
|
-
else diag(FIELD_NODE_MAP.get(f), `Table already has a field called "${f.name}"`);
|
|
479
|
-
return set;
|
|
480
|
-
}, {});
|
|
607
|
+
syntaxNode.getChildren("ColumnDef").forEach((cn) => addColumnField(table2, cn));
|
|
608
|
+
syntaxNode.getChildren("JoinDef").forEach((jn) => addJoinField(table2, jn));
|
|
609
|
+
syntaxNode.getChildren("ComputedDef").forEach((cn) => addComputedField(table2, cn));
|
|
481
610
|
TABLE_NODE_MAP.set(table2, syntaxNode);
|
|
482
611
|
fi.tables.push(table2);
|
|
483
612
|
}
|
|
@@ -486,12 +615,56 @@ function makeTable(name, type) {
|
|
|
486
615
|
let tablePath = config.namespace ? `${config.namespace}.${name}` : name;
|
|
487
616
|
return { name, type, fields: [], connection: config.dialect, dialect: config.dialect, tableName: name, tablePath, metadata: {} };
|
|
488
617
|
}
|
|
618
|
+
function addColumnField(table2, node) {
|
|
619
|
+
let name = txt(node.getChild("Identifier"));
|
|
620
|
+
if (node.getChild("PrimaryKey")) {
|
|
621
|
+
if (table2.primaryKey) diag(node, `Table ${table2.name} has multiple primary keys`);
|
|
622
|
+
table2.primaryKey = name;
|
|
623
|
+
}
|
|
624
|
+
let type = convertDataType(txt(node.getChild("DataType")));
|
|
625
|
+
if (!type) diag(node, `Unsupported data type: ${txt(node.getChild("DataType"))}`);
|
|
626
|
+
let field = { name, type, metadata: extractLeadingMetadata(node) };
|
|
627
|
+
return addFieldToTable(table2, field, node);
|
|
628
|
+
}
|
|
629
|
+
function addJoinField(table2, node) {
|
|
630
|
+
let nameNode = node.getChild("Alias") || node.getChild("Identifier");
|
|
631
|
+
return addFieldToTable(table2, { name: txt(nameNode) }, node);
|
|
632
|
+
}
|
|
633
|
+
function addComputedField(table2, node) {
|
|
634
|
+
let name = txt(node.getChild("Alias"));
|
|
635
|
+
addFieldToTable(table2, { name, metadata: extractLeadingMetadata(node) }, node);
|
|
636
|
+
}
|
|
637
|
+
function addFieldToTable(table2, field, node) {
|
|
638
|
+
if (table2.fields.find((f) => f.name == field.name)) {
|
|
639
|
+
diag(node, `Table already has a field called "${field.name}"`);
|
|
640
|
+
return null;
|
|
641
|
+
}
|
|
642
|
+
table2.fields.push(field);
|
|
643
|
+
FIELD_NODE_MAP.set(field, node);
|
|
644
|
+
return field;
|
|
645
|
+
}
|
|
646
|
+
function applyExtends(fi) {
|
|
647
|
+
fi.tree.topNode.getChildren("ExtendStatement").forEach((node) => {
|
|
648
|
+
let tableName = txt(node.getChild("Identifier"));
|
|
649
|
+
let target = lookupTable(tableName, node);
|
|
650
|
+
if (!target) {
|
|
651
|
+
return diag(node.getChild("Identifier") || node, `Cannot extend unknown table "${tableName}"`);
|
|
652
|
+
}
|
|
653
|
+
node.getChildren("JoinDef").forEach((jn) => addJoinField(target, jn));
|
|
654
|
+
node.getChildren("ComputedDef").forEach((cn) => addComputedField(target, cn));
|
|
655
|
+
});
|
|
656
|
+
}
|
|
489
657
|
function analyzeTable(table2) {
|
|
490
|
-
if (table2.type == "query_source")
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
658
|
+
if (table2.type == "query_source") {
|
|
659
|
+
if (table2.query) return;
|
|
660
|
+
let node = TABLE_NODE_MAP.get(table2);
|
|
661
|
+
let query = analyzeQuery(node.getChild("QueryStatement"));
|
|
662
|
+
if (!query) return;
|
|
663
|
+
let queryFields = query.fields.map((f) => ({ type: f.type, name: f.name, metadata: f.metadata }));
|
|
664
|
+
table2.fields.push(...queryFields);
|
|
665
|
+
table2.query = query;
|
|
494
666
|
}
|
|
667
|
+
table2.fields.map((f) => analyzeField(f, table2));
|
|
495
668
|
}
|
|
496
669
|
function analyzeField(field, table2) {
|
|
497
670
|
if (field.type) return;
|
|
@@ -504,8 +677,9 @@ function analyzeField(field, table2) {
|
|
|
504
677
|
field = field;
|
|
505
678
|
let target = lookupTable(txt(node.getChild("Identifier")), node);
|
|
506
679
|
if (!target) return diag(node, "Unknown table to join");
|
|
507
|
-
if (target.type == "query_source")
|
|
508
|
-
let
|
|
680
|
+
if (target.type == "query_source") analyzeTable(target);
|
|
681
|
+
let joinTypeStr = txt(node.getChild("JoinType")).replace(/\s+/g, " ");
|
|
682
|
+
let jt = { "join many": "many", "join one": "one" }[joinTypeStr];
|
|
509
683
|
if (!jt) return diag(node, "Unknown join type");
|
|
510
684
|
Object.assign(field, target, { name: field.name, join: jt });
|
|
511
685
|
field.onExpression = analyzeExpression(node.getChild("Expression"), { table: table2, outputFields: [] });
|
|
@@ -517,37 +691,29 @@ function analyzeField(field, table2) {
|
|
|
517
691
|
}
|
|
518
692
|
analysisQueue.delete(field);
|
|
519
693
|
}
|
|
520
|
-
function analyzeQueryTable(table2) {
|
|
521
|
-
if (table2.query) return;
|
|
522
|
-
let node = TABLE_NODE_MAP.get(table2);
|
|
523
|
-
let query = analyzeQuery(node.getChild("QueryStatement"));
|
|
524
|
-
if (!query) throw new Error("Couldnt find query in QueryStatement");
|
|
525
|
-
table2.fields = query.fields.map((f) => ({ type: f.type, name: f.name, metadata: f.metadata }));
|
|
526
|
-
table2.query = query.malloyQuery;
|
|
527
|
-
if (typeof table2.query.structRef == "string") {
|
|
528
|
-
table2.query.structRef = lookupTable(table2.query.structRef, node);
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
694
|
function analyzeQuery(queryNode) {
|
|
532
|
-
let
|
|
695
|
+
let baseTableName;
|
|
533
696
|
let scope = { table: null, outputFields: [] };
|
|
534
697
|
let isAgg = false;
|
|
535
|
-
let subQuerySources = [];
|
|
536
698
|
if (!txt(queryNode)) return;
|
|
699
|
+
if (txt(queryNode).trim().toLowerCase() == "select 1") {
|
|
700
|
+
let fields = [{ name: "col_0", type: "number", metadata: {}, e: { node: "numberLiteral", literal: "1", type: "number" } }];
|
|
701
|
+
return { fields, baseTableName: "", rawSql: "select 1", structRef: {}, pipeline: [] };
|
|
702
|
+
}
|
|
537
703
|
let froms = queryNode.getChild("FromClause")?.getChildren("TablePrimary") || [];
|
|
538
704
|
if (froms.find((f) => f.name == "JoinClause")) diag(froms[0], "Query joins not yet supported");
|
|
539
|
-
if (froms.length == 0) diag(queryNode, "No tables in FROM clause");
|
|
705
|
+
if (froms.length == 0) return diag(queryNode, "No tables in FROM clause");
|
|
540
706
|
if (froms.length > 1) diag(froms[0], "Multiple tables/joins in FROM clause not yet supported");
|
|
541
707
|
if (froms[0].name == "Subquery") {
|
|
542
|
-
|
|
543
|
-
|
|
708
|
+
diag(froms[0], "Graphene doesn't yet support subqueries. Try chaining queries instead.");
|
|
709
|
+
baseTableName = txt(froms[0].getChild("Alias")) || "subquery";
|
|
710
|
+
scope.table = makeTable(baseTableName, "query_source");
|
|
544
711
|
TABLE_NODE_MAP.set(scope.table, froms[0].getChild("SubqueryExpression"));
|
|
545
|
-
|
|
546
|
-
subQuerySources.push(scope.table);
|
|
712
|
+
analyzeTable(scope.table);
|
|
547
713
|
} else {
|
|
548
|
-
|
|
549
|
-
scope.table = lookupTable(
|
|
550
|
-
if (!scope.table) return diag(froms[0], `could not find table "${
|
|
714
|
+
baseTableName = txt(froms[0].getChild("Identifier"));
|
|
715
|
+
scope.table = lookupTable(baseTableName, froms[0]);
|
|
716
|
+
if (!scope.table) return diag(froms[0], `could not find table "${baseTableName}"`);
|
|
551
717
|
NODE_ENTITY_MAP.set(froms[0], { entityType: "table", table: scope.table });
|
|
552
718
|
}
|
|
553
719
|
let selects = queryNode.getChild("SelectClause")?.getChildren("SelectItem") || [];
|
|
@@ -555,11 +721,12 @@ function analyzeQuery(queryNode) {
|
|
|
555
721
|
isAgg ||= !!isSelectDistinct;
|
|
556
722
|
selects.forEach((s) => {
|
|
557
723
|
if (s.getChild("Wildcard")) {
|
|
558
|
-
let
|
|
559
|
-
let pathStrings =
|
|
560
|
-
let target = followJoins(
|
|
724
|
+
let path10 = s.getChild("Wildcard").getChildren("Identifier");
|
|
725
|
+
let pathStrings = path10.map((p) => txt(p));
|
|
726
|
+
let target = followJoins(path10, scope.table);
|
|
561
727
|
if (!target) return;
|
|
562
728
|
target.fields.forEach((f) => {
|
|
729
|
+
analyzeField(f, target);
|
|
563
730
|
if (isJoin(f) || f.isAgg) return;
|
|
564
731
|
scope.outputFields.push({ ...f, e: { node: "field", path: [...pathStrings, f.name], type: f.type } });
|
|
565
732
|
});
|
|
@@ -613,22 +780,21 @@ function analyzeQuery(queryNode) {
|
|
|
613
780
|
}
|
|
614
781
|
let q = {
|
|
615
782
|
fields: scope.outputFields,
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
}
|
|
783
|
+
baseTableName,
|
|
784
|
+
type: "query",
|
|
785
|
+
structRef: null,
|
|
786
|
+
// We fill this in as part of `toSql`
|
|
787
|
+
pipeline: [{
|
|
788
|
+
type: isAgg ? "reduce" : "project",
|
|
789
|
+
queryFields: scope.outputFields,
|
|
790
|
+
filterList,
|
|
791
|
+
outputStruct: null,
|
|
792
|
+
isRepeated: false,
|
|
793
|
+
orderBy: orderByList.length ? orderByList : void 0,
|
|
794
|
+
limit: queryLimit
|
|
795
|
+
}]
|
|
630
796
|
};
|
|
631
|
-
inferParamTypes(q
|
|
797
|
+
inferParamTypes(q);
|
|
632
798
|
return q;
|
|
633
799
|
}
|
|
634
800
|
function analyzeExpression(expr, scope) {
|
|
@@ -655,11 +821,13 @@ function analyzeExpression(expr, scope) {
|
|
|
655
821
|
if (scope.outputFields.includes(field) && field.isAgg) {
|
|
656
822
|
return { node: "outputField", name: field.name, ...typeInfo, isAgg: field.isAgg };
|
|
657
823
|
}
|
|
658
|
-
let
|
|
659
|
-
return { node: "field", path:
|
|
824
|
+
let path10 = expr.getChildren("Identifier").map((i) => txt(i));
|
|
825
|
+
return { node: "field", path: path10, ...typeInfo, isAgg: field.isAgg };
|
|
660
826
|
}
|
|
661
827
|
case "ExtractExpression": {
|
|
662
|
-
let
|
|
828
|
+
let extractExprNode = expr.getChild("Expression");
|
|
829
|
+
let e = analyzeExpression(extractExprNode, scope);
|
|
830
|
+
checkTypes(e, ["date", "timestamp"], extractExprNode);
|
|
663
831
|
if (!isTemporalType(e.type) || !e.typeDef) return diag(expr, "Expression must be a date or timestamp", errExpr);
|
|
664
832
|
let units = txt(expr.getChild("ExtractUnit")).replace(/^['"]|['"]$/g, "").toLowerCase();
|
|
665
833
|
if (!isExtractUnit(units)) return diag(expr, "Not a valid unit to extract", errExpr);
|
|
@@ -682,7 +850,28 @@ function analyzeExpression(expr, scope) {
|
|
|
682
850
|
let left2 = analyzeExpression(expr.firstChild, scope);
|
|
683
851
|
let right2 = analyzeExpression(expr.lastChild, scope);
|
|
684
852
|
let op = txt(expr.firstChild?.nextSibling).toLowerCase();
|
|
685
|
-
|
|
853
|
+
let type = left2.type;
|
|
854
|
+
if (op == "or" || op == "and") type = "boolean";
|
|
855
|
+
if (op == "+" || op == "-") {
|
|
856
|
+
if (["date", "timestamp", "interval"].find((t) => left2.type == t || right2.type == t)) {
|
|
857
|
+
return analyzeTimeExpression(op, left2, right2, expr);
|
|
858
|
+
}
|
|
859
|
+
ensureSameType(left2, expr.firstChild, right2, expr.lastChild);
|
|
860
|
+
}
|
|
861
|
+
if (op == "*" || op == "/" || op == "%") {
|
|
862
|
+
checkTypes(left2, ["number"], expr.firstChild);
|
|
863
|
+
checkTypes(right2, ["number"], expr.lastChild);
|
|
864
|
+
}
|
|
865
|
+
if (op == "<" || op == "<=" || op == ">" || op == ">=" || op == "=" || op == "!=" || op == "<>") {
|
|
866
|
+
ensureSameType(left2, expr.firstChild, right2, expr.lastChild);
|
|
867
|
+
type = "boolean";
|
|
868
|
+
}
|
|
869
|
+
if (op == "like" || op == "ilike") {
|
|
870
|
+
checkTypes(left2, ["string"], expr.firstChild);
|
|
871
|
+
checkTypes(right2, ["string"], expr.lastChild);
|
|
872
|
+
type = "boolean";
|
|
873
|
+
}
|
|
874
|
+
return { node: op, kids: { left: left2, right: right2 }, type, isAgg: left2.isAgg || right2.isAgg };
|
|
686
875
|
}
|
|
687
876
|
case "NullTestExpression": {
|
|
688
877
|
let node = expr.getChildren("Kw").find((n) => txt(n).toLowerCase() == "not") ? "is-not-null" : "is-null";
|
|
@@ -716,8 +905,13 @@ function analyzeExpression(expr, scope) {
|
|
|
716
905
|
let oneOf = [];
|
|
717
906
|
let valueList = expr.getChild("InValueList");
|
|
718
907
|
if (valueList) {
|
|
719
|
-
oneOf = valueList.getChildren("Expression").map((v) =>
|
|
908
|
+
oneOf = valueList.getChildren("Expression").map((v) => {
|
|
909
|
+
let e = analyzeExpression(v, scope);
|
|
910
|
+
checkTypes(e, [eNode.type], v);
|
|
911
|
+
return e;
|
|
912
|
+
});
|
|
720
913
|
} else {
|
|
914
|
+
diag(expr, "IN (<subquery>) is not yet supported");
|
|
721
915
|
oneOf = [{ node: "genericSQLExpr", kids: { args: [] }, type: "array" }];
|
|
722
916
|
}
|
|
723
917
|
let isAgg = eNode.isAgg || oneOf.some((v) => v.isAgg);
|
|
@@ -735,11 +929,14 @@ function analyzeFunctionCall(expr, scope) {
|
|
|
735
929
|
return o.params.length == argNodes.length || !!o.params.find((p) => p.isVariadic);
|
|
736
930
|
});
|
|
737
931
|
let args = argNodes.map((node, idx) => {
|
|
738
|
-
let
|
|
739
|
-
if (
|
|
932
|
+
let firstType = overload?.params[idx]?.allowedTypes[0];
|
|
933
|
+
if (firstType?.type === "sql native" && firstType?.rawType === "kw") {
|
|
740
934
|
return { node: "genericSQLExpr", kids: { args: [] }, type: "sql native", src: [txt(node)], isAgg: false };
|
|
741
935
|
} else {
|
|
742
|
-
|
|
936
|
+
let argExpr = analyzeExpression(node, scope);
|
|
937
|
+
let allowed = overload?.params[idx]?.allowedTypes.map((at) => at.type);
|
|
938
|
+
if (allowed) checkTypes(argExpr, allowed, node);
|
|
939
|
+
return argExpr;
|
|
743
940
|
}
|
|
744
941
|
});
|
|
745
942
|
let type = overload?.returnType.type;
|
|
@@ -754,7 +951,12 @@ function analyzeFunctionCall(expr, scope) {
|
|
|
754
951
|
}));
|
|
755
952
|
let ret;
|
|
756
953
|
if (["count", "min", "max", "avg", "sum"].includes(name.toLowerCase())) {
|
|
757
|
-
|
|
954
|
+
let type2 = "number", typeDef;
|
|
955
|
+
if (["min", "max", "avg"].includes(name.toLowerCase())) {
|
|
956
|
+
type2 = args[0].type;
|
|
957
|
+
typeDef = args[0].typeDef;
|
|
958
|
+
}
|
|
959
|
+
ret = { node: "aggregate", function: name, e: args[0], type: type2, typeDef, isAgg: true };
|
|
758
960
|
} else if (overload && type) {
|
|
759
961
|
ret = {
|
|
760
962
|
node: "function_call",
|
|
@@ -775,8 +977,52 @@ function analyzeFunctionCall(expr, scope) {
|
|
|
775
977
|
if (foriegnPaths.length > 0) ret.structPath = foriegnPaths[0].split(".");
|
|
776
978
|
return ret;
|
|
777
979
|
}
|
|
980
|
+
function analyzeTimeExpression(op, left2, right2, node) {
|
|
981
|
+
if (left2.type !== "date" && left2.type !== "timestamp") return diag(node, "Expected left side to be a date or timestamp", errExpr);
|
|
982
|
+
let units = left2.type === "timestamp" ? "second" : "day";
|
|
983
|
+
if (right2.node == "stringLiteral") {
|
|
984
|
+
units = parseTemporal(right2);
|
|
985
|
+
if (right2.node == "stringLiteral") {
|
|
986
|
+
return diag(node, "Could not parse interval", errExpr);
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
if (right2.type == "date" || right2.type == "timestamp") {
|
|
990
|
+
if (op !== "-") return diag(node, "Only subtraction between dates is supported", errExpr);
|
|
991
|
+
if (right2.type !== left2.type) return diag(node, `Expected right side to be a ${left2.type}`, errExpr);
|
|
992
|
+
return { node: "timeDiff", kids: { left: left2, right: right2 }, units, type: "interval", isAgg: false };
|
|
993
|
+
}
|
|
994
|
+
if (right2.type == "interval") {
|
|
995
|
+
let typeDef = { type: left2.type };
|
|
996
|
+
return { node: "delta", kids: { base: left2, delta: right2 }, op, units, type: left2.type, typeDef, isAgg: false };
|
|
997
|
+
}
|
|
998
|
+
return diag(node, "Expected right side to be a date or interval", errExpr);
|
|
999
|
+
}
|
|
1000
|
+
function ensureSameType(left2, leftNode, right2, rightNode) {
|
|
1001
|
+
if (left2.type === "error" || right2.type === "error") return;
|
|
1002
|
+
if (left2.node === "parameter" || right2.node === "parameter") return;
|
|
1003
|
+
if (isTemporalType(left2.type)) checkTypes(right2, [left2.type], rightNode);
|
|
1004
|
+
if (isTemporalType(right2.type)) checkTypes(left2, [right2.type], leftNode);
|
|
1005
|
+
if (left2.type !== right2.type) diag(rightNode, `Expected ${left2.type}, got ${right2.type}`);
|
|
1006
|
+
}
|
|
1007
|
+
function checkTypes(expr, expected, node) {
|
|
1008
|
+
if (expr.type === "error") return;
|
|
1009
|
+
if (expr.node === "parameter") return;
|
|
1010
|
+
if (expected.includes(expr.type)) return;
|
|
1011
|
+
if (expected.includes("generic")) return;
|
|
1012
|
+
let dt = expected.find((t) => t == "date") || expected.find((t) => t == "timestamp");
|
|
1013
|
+
if (expr.node == "stringLiteral" && dt) {
|
|
1014
|
+
let parsed = parseTemporalLiteral(expr.literal, dt);
|
|
1015
|
+
if (!parsed) return diag(node, `Could not parse ${dt} literal: "${expr.literal}"`, void 0);
|
|
1016
|
+
let typeDef = { type: parsed.type, timeframe: parsed.timeframe };
|
|
1017
|
+
Object.assign(expr, { node: "timeLiteral", literal: parsed?.literal, type: parsed?.type, typeDef });
|
|
1018
|
+
} else if (expr.node == "stringLiteral" && expected.includes("interval")) {
|
|
1019
|
+
let parsed = parseIntervalLiteral(expr.literal);
|
|
1020
|
+
if (!parsed) return diag(node, `Could not parse interval literal: "${expr.literal}"`, void 0);
|
|
1021
|
+
return Object.assign(expr, { node: "numberLiteral", literal: parsed.quantity.toString(), type: "interval", intervalUnit: parsed.unit });
|
|
1022
|
+
} else diag(node, `Expected types: ${expected.join(", ")}`);
|
|
1023
|
+
}
|
|
778
1024
|
function isSupportedType(value) {
|
|
779
|
-
let supported = ["string", "number", "boolean", "date", "timestamp", "json", "sql native", "error", "array", "record", "null", "generic"];
|
|
1025
|
+
let supported = ["string", "number", "boolean", "date", "timestamp", "json", "sql native", "error", "array", "record", "null", "generic", "interval"];
|
|
780
1026
|
return supported.includes(value);
|
|
781
1027
|
}
|
|
782
1028
|
function lookupField(expr, scope) {
|
|
@@ -862,6 +1108,10 @@ function convertDataType(dataType) {
|
|
|
862
1108
|
return "number";
|
|
863
1109
|
case "INT64":
|
|
864
1110
|
return "number";
|
|
1111
|
+
case "NUMBER":
|
|
1112
|
+
return "number";
|
|
1113
|
+
case "VARIANT":
|
|
1114
|
+
return "string";
|
|
865
1115
|
case "TEXT":
|
|
866
1116
|
return "string";
|
|
867
1117
|
case "STRING":
|
|
@@ -884,6 +1134,8 @@ function convertDataType(dataType) {
|
|
|
884
1134
|
return "timestamp";
|
|
885
1135
|
case "TIMESTAMP":
|
|
886
1136
|
return "timestamp";
|
|
1137
|
+
case "TIMESTAMP_NTZ":
|
|
1138
|
+
return "timestamp";
|
|
887
1139
|
case "DECIMAL":
|
|
888
1140
|
return "number";
|
|
889
1141
|
case "DOUBLE":
|
|
@@ -907,11 +1159,13 @@ function convertDataType(dataType) {
|
|
|
907
1159
|
var FILE_MAP, diagnostics, TABLE_NODE_MAP, FIELD_NODE_MAP, NODE_ENTITY_MAP, analysisQueue, errExpr;
|
|
908
1160
|
var init_analyze = __esm({
|
|
909
1161
|
"../lang/analyze.ts"() {
|
|
1162
|
+
"use strict";
|
|
910
1163
|
init_util();
|
|
911
1164
|
init_metadata();
|
|
912
1165
|
init_config();
|
|
913
1166
|
init_functions();
|
|
914
1167
|
init_params();
|
|
1168
|
+
init_temporalLiterals();
|
|
915
1169
|
FILE_MAP = {};
|
|
916
1170
|
diagnostics = [];
|
|
917
1171
|
TABLE_NODE_MAP = /* @__PURE__ */ new WeakMap();
|
|
@@ -923,39 +1177,40 @@ var init_analyze = __esm({
|
|
|
923
1177
|
});
|
|
924
1178
|
|
|
925
1179
|
// ../lang/parser.terms.js
|
|
926
|
-
var table, primary_key, as, on, not,
|
|
1180
|
+
var table, primary_key, join, as, on, or, and, like, not, _in, from, inner, left, right, full, cross, select, where, group, by, order, asc, desc, limit, offset, is, _null, exists, _true, _false;
|
|
927
1181
|
var init_parser_terms = __esm({
|
|
928
1182
|
"../lang/parser.terms.js"() {
|
|
1183
|
+
"use strict";
|
|
929
1184
|
table = 6;
|
|
930
1185
|
primary_key = 11;
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
1186
|
+
join = 15;
|
|
1187
|
+
as = 21;
|
|
1188
|
+
on = 24;
|
|
1189
|
+
or = 31;
|
|
1190
|
+
and = 34;
|
|
1191
|
+
like = 43;
|
|
1192
|
+
not = 45;
|
|
1193
|
+
_in = 55;
|
|
1194
|
+
from = 60;
|
|
1195
|
+
inner = 66;
|
|
1196
|
+
left = 68;
|
|
1197
|
+
right = 70;
|
|
1198
|
+
full = 72;
|
|
1199
|
+
cross = 74;
|
|
1200
|
+
select = 77;
|
|
1201
|
+
where = 84;
|
|
1202
|
+
group = 87;
|
|
1203
|
+
by = 89;
|
|
1204
|
+
order = 95;
|
|
1205
|
+
asc = 99;
|
|
1206
|
+
desc = 101;
|
|
1207
|
+
limit = 104;
|
|
1208
|
+
offset = 106;
|
|
952
1209
|
is = 109;
|
|
953
1210
|
_null = 111;
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
and = 132;
|
|
958
|
-
like = 141;
|
|
1211
|
+
exists = 129;
|
|
1212
|
+
_true = 133;
|
|
1213
|
+
_false = 135;
|
|
959
1214
|
}
|
|
960
1215
|
});
|
|
961
1216
|
|
|
@@ -967,6 +1222,7 @@ function specializeIdentifier(value) {
|
|
|
967
1222
|
var keywords;
|
|
968
1223
|
var init_tokens = __esm({
|
|
969
1224
|
"../lang/tokens.js"() {
|
|
1225
|
+
"use strict";
|
|
970
1226
|
init_parser_terms();
|
|
971
1227
|
keywords = {
|
|
972
1228
|
select,
|
|
@@ -1008,25 +1264,26 @@ import { LRParser } from "@lezer/lr";
|
|
|
1008
1264
|
var spec_Identifier, parser;
|
|
1009
1265
|
var init_parser = __esm({
|
|
1010
1266
|
"../lang/parser.js"() {
|
|
1267
|
+
"use strict";
|
|
1011
1268
|
init_tokens();
|
|
1012
|
-
spec_Identifier = { __proto__: null, table: 12, primary_key: 22,
|
|
1269
|
+
spec_Identifier = { __proto__: null, table: 12, primary_key: 22, join: 30, one: 34, many: 38, as: 42, on: 48, or: 62, and: 68, like: 86, not: 90, in: 110, from: 120, inner: 132, left: 136, right: 140, full: 144, cross: 148, select: 154, distinct: 158, where: 168, group: 174, by: 178, having: 184, order: 190, asc: 198, desc: 202, limit: 208, offset: 212, is: 218, null: 222, case: 232, when: 238, then: 242, else: 248, end: 252, exists: 258, true: 266, false: 270, count: 280, extract: 286, extend: 306 };
|
|
1013
1270
|
parser = LRParser.deserialize({
|
|
1014
1271
|
version: 14,
|
|
1015
|
-
states: "
|
|
1016
|
-
stateData: "
|
|
1017
|
-
goto: "
|
|
1018
|
-
nodeNames: "\u26A0 Comment Program TableStatement Kw Identifier table ColumnDef DataType PrimaryKey Kw primary_key JoinDef JoinType Kw
|
|
1019
|
-
maxTerm:
|
|
1272
|
+
states: "G`QYQPOOOOQO'#C`'#C`OOQO'#Di'#DiOwQPO'#DhOOQO'#Dz'#DzO#TQPO'#DyOOQO'#ER'#EROOQO'#EU'#EUO#[QPO'#ETOOQO'#EZ'#EZOOQO'#E^'#E^O#[QPO'#E]OOQO'#Eg'#EgO#aQPO'#EfOOQO'#Fp'#FpO#}QPO'#DgO$[QPO'#C_OOQO'#Fj'#FjO$aQPO'#FiO$fQPO'#FlQYQPOOQ%ZQPO'#FlO%`QPO'#EQO%`QPO'#EYO&aQPO'#DkO#fQPO'#DmO'nQPO'#DlOOQO'#GP'#GPO)aQPO,5:SO+hQPO'#CvO-pQPO'#CvOOQO'#DY'#DYO-xQPO'#DmOOQO'#D|'#D|OOQO'#EP'#EPOOQO'#EO'#EOO.cQPO,5:eO!PQPO,5:eO0eQPO'#EOOOQO'#En'#EnOOQO'#Eq'#EqOOQO'#Es'#EsO1_QPO'#ErOOQO'#FQ'#FQO1fQPO'#FPOOQO'#FU'#FUOOQO'#FW'#FWOOQO'#FT'#FTOOQO'#FY'#FYOOQO'#GT'#GTOOQO'#F]'#F]O1kQPO'#F[OOQO'#GS'#GSOOQO'#F`'#F`OOQO'#GR'#GROOQO'#GU'#GUOOQO'#F}'#F}O%`QPO'#EpO1pQPO'#F_OOQO'#EW'#EWO!PQPO,5:oO1uQPO,5:wO1}QPO,5;QOOQO-E9n-E9nO2rQPO,58yO2zQPO,5<TOOQO,5<W,5<WOOQO-E9j-E9jO3PQPO'#CvO3UQPO,5:lO3xQPO,5:tOOQO'#Cp'#CpOOQO'#Cr'#CrOOQO,5:V,5:VO4lQPO,5:VO4qQPO,5:XOOQO,5:W,5:WO4lQPO,5:WOOQO'#Cj'#CjOOQO'#Do'#DoOOQO'#Dq'#DqOOQO'#Ds'#DsOOQO'#Du'#DuOOQO'#Dw'#DwOwQPO'#DnO4vQPO'#DnOOQO'#Fq'#FqO4{QPO1G/nOOQO,5<Y,5<YO5oQPO,5;uO5vQPO,59bOOQO-E9l-E9lOOQO,5:k,5:kO9[QPO,5<OO9cQPO1G0PO:VQPO1G0PO:VQPO1G0POOQO'#Cz'#CzOOQO'#C}'#C}OOQO'#DW'#DWOOQO'#DP'#DPO:zQPO,59}OOQO'#D['#D[OOQO'#D_'#D_OOQO'#Dd'#DdO;SQPO,59}OOQO'#El'#ElO;XQPO,5;VO%`QPO,59hO%`QPO,59hO%`QPO,59hO%`QPO,59hO%`QPO,59hOOQO,5:j,5:jO4lQPO,5:jO;aQPO,5;^OOQO'#Ev'#EvOOQO'#Ft'#FtO;hQPO,5;^O%`QPO'#EuO#fQPO,5;kO;sQPO,5;vOOQO,5;[,5;[O<QQPO,5;yO<YQPO1G0ZO<}QPO'#E`O=xQPO1G0cOOQO'#Ei'#EiO>mQPO1G0lO>rQPO1G.eO?sQPO1G1nO?xQPO1G1oOOQO1G/q1G/qOOQO1G/s1G/sOOQO1G/r1G/rO@yQPO,5:YOwQPO,5:YOOQO-E9o-E9oOBQQPO1G1aOOQO1G1a1G1aPB[QPO'#FnOOQO1G1j1G1jOOQO,5<^,5<^OBaQPO7+%kOOQO-E9p-E9pOCTQPO7+%kOOQO,59k,59kOCxQPO1G/iO-xQPO1G/iOOQO1G0q1G0qO;[QPO1G0qOG]QPO1G/SOGdQPO1G/SOJvQPO1G/SOKQQPO1G/SOOQO1G/S1G/SOOQO1G0U1G0UO;hQPO1G0xOOQO-E9r-E9rOOQO'#E{'#E{OOQO'#E}'#E}OOQO1G0x1G0xO;nQPO1G0xO%`QPO'#EzOK[QPO,5;aOKcQPO1G1VOKhQPO1G1bOOQO1G1b1G1bOKoQPO1G1bO%`QPO1G1bOOQO'#Fb'#FbOKtQPO1G1eOKyQPO7+%uOLmQPO7+%uOOQO'#Eb'#EbOOQO'#Ed'#EdOOQO,5:z,5:zONSQPO7+%}ON^QPO7+%}OOQO7+&W7+&WONeQPO'#CrONoQPO'#CiONwQPO'#ChO/WQPO'#CxOOQO'#F|'#F|OOQO'#Fz'#FzON|QPO'#FmO!!TQPO7+$PO!![QPO'#CxO#fQPO7+'YONhQPO'#CrOOQO'#GW'#GWO!!aQPO'#FuO!#hQPO7+'ZOOQO'#Cs'#CsO%`QPO1G/tO!#oQPO1G/tO!$vQPO7+&{O!%OQPO7+&{OOQO7+&{7+&{P!PQPO'#FrO!%VQPO<<IVO-xQPO7+%TO!%yQPO'#DfO!&TQPO7+%TOOQO7+&]7+&]OOQO7+&d7+&dO;nQPO7+&dO!&YQPO,5;fOOQO'#Ex'#ExO%`QPO1G0{OOQO7+&q7+&qOOQO7+&|7+&|O!&aQPO7+&|O%`QPO7+'PO!&hQPO<<IaOOQO,5<_,5<_O!'[QPO<<IiOOQO-E9q-E9qOOQO'#Cd'#CdO!(SQPO,58}OOQO'#Cl'#ClOOQO'#Cn'#CnOOQO,59T,59TO!)^QPO,59SO:zQPO,5<PO!)fQPO,5<PO;XQPO,5<QO%`QPO,59eO%`QPO,59eO%`QPO,59eO%`QPO,59eO%`QPO,59eO4lQPO,59dOOQO,5<X,5<XOOQO-E9k-E9kOOQO<<Gk<<GkO%`QPO,59dO!)kQPO<<JtOOQO,5<a,5<aOOQO-E9s-E9sOOQO<<Ju<<JuO!)pQPO7+%`O%`QPO7+%`OOQO-E9m-E9mO!*vQPO<<JgOOQO<<Jg<<JgO!*}QPO,5<ZO!+XQPO<<HoO!+^QPO,5:QO!+fQPO,5:QOOQO<<Ho<<HoOOQO<<JO<<JOO!+mQPO7+&gOOQO<<Jh<<JhO!+zQPO<<JkP1uQPO'#FsOOQO'#Cf'#CfOOQO'#Ce'#CeOOQO1G.i1G.iO!,RQPO1G.nO4lQPO1G.nO!,WQPO1G1kO-xQPO1G1kOOQO1G1l1G1lO;[QPO1G1lO!-gQPO1G/PO!-nQPO1G/PO!.|QPO1G/PO!/WQPO1G/POOQO1G/P1G/POOQO1G/O1G/OO!/bQPO1G/OOOQOAN@`AN@`O!0bQPO<<HzP%`QPO'#FoOOQOAN@RAN@ROOQOAN>ZAN>ZO!1hQPO1G/lOOQOAN@VAN@VO!1oQPO'#CvO!2|QPO'#CuOOQO7+$Y7+$YO!)aQPO7+$YO-xQPO7+'VO!3RQPO7+'VOOQO7+'W7+'WO!,RQPO,59aO!,RQPO<<GtO!3WQPO<<JqOOQO<<Jq<<JqOOQO1G.{1G.{OOQOAN=`AN=`OOQOAN@]AN@]",
|
|
1273
|
+
stateData: "!3b~O$lOSPOS~OUPO!^QO!oSO!vUO!yVO#OXO#RYO#[[O$_aO~OThO$miO~OTmO}oO!PxO!QxO!SrO#T!RO#cwO#hyO#u{O#v!RO#y}O#{!OO$Q!SO$T!VO$V!WO$mpO~O!qqO~P!PO!{!]O~O#T!`O~O!^QO!oSO!vUO!yVO#OXO#RYO#[[O~O$j!ZX$y!ZX$t!ZX~P#fOT!bO~OT!cO~O$y!dOU$`X!^$`X!o$`X!v$`X!y$`X#O$`X#R$`X#[$`X$_$`X$j$`X~O$y!dO~OTmO}oO!PxO!QxO#T!RO#cwO#hyO#u{O#v!RO#y}O#{!OO$Q!SO$T!VO$V!WO$mpO~OT!jOe!iO_!_X!^!_X!d!_X!f!_X!h!_X!j!_X!l!_X!o!_X!v!_X!y!_X#O!_X#R!_X#[!_X$j!_X$y!_X$t!_Xh!_X~OT!jOe!iO_!`X!^!`X!d!`X!f!`X!h!`X!j!`X!l!`X!o!`X!v!`X!y!`X#O!`X#R!`X#[!`X$j!`X$y!`X$t!`Xh!`X~O_!pO!d!qO!f!rO!h!sO!j!tO!l!uO~O!^![a!o![a!v![a!y![a#O![a#R![a#[![a$j![a$y![a$t![a~P({O$m!{O$o!zOejXkjXojXrjXtjXujXvjXwjXxjXyjX{jX}jX!PjX!QjX!SjX!TjX!UjX!XjX#ajX~OTjX!^jX!ojX!vjX!yjX#OjX#RjX#[jX$jjX$rjX$yjX$tjX#kjX#mjX#rjX_jX!djX!fjX!hjX!jjX!ljX#pjX#TjX#cjX#hjX#ujX#vjX#yjX#{jX$QjX$TjX$VjX~P*TOT!|O!S#OO~O!^QO!oSO!vUO!yVO#OXO#RYO#[[O~P%`O$r#QO!^!ma!o!ma!v!ma!y!ma#O!ma#R!ma#[!ma$j!ma$y!ma$t!ma~Oe!iOk#WOo#TOr#UOt#WOu#WOv#WOw#WOx#WOy#WO{#VO}oO!P#YO!Q#YO!S#ZO!T#ZO!U#ZO!X#[O#a#^O~OT!jO!^!rX!o!rX!v!rX!y!rX#O!rX#R!rX#[!rX$j!rX$r!rX$y!rX$t!rX~P/WO#k#hO~P%`O$m#lO~O$m#mO~O$m#oO~OT#qO#T#qO~O#^#sO!^#Ya!o#Ya!v#Ya!y#Ya#O#Ya#R#Ya#[#Ya$j#Ya$y#Ya$t#Ya~Oe!iO$m#uO~O$m#wO~OT!|O~O!^!ta!o!ta!v!ta!y!ta#O!ta#R!ta#[!ta$j!ta$y!ta$t!ta~P/ZO!^!|a!o!|a!v!|a!y!|a#O!|a#R!|a#[!|a$j!|a$y!|a$t!|a~P/ZOT!jO~O$t#yO~O_!pO~O!^![i!o![i!v![i!y![i#O![i#R![i#[![i$j![i$y![i$t![i~P({O$t$PO~P%`O$o!zOTjaejakjaojarjatjaujavjawjaxjayja{ja}ja!Pja!Qja!Sja!Tja!Uja!Xja!^ja!oja!vja!yja#Oja#Rja#[ja#aja$jja$rja$yja$tja#kja#mja#rja_ja!dja!fja!hja!jja!lja#pja#Tja#cja#hja#uja#vja#yja#{ja$Qja$Tja$Vja~O$t$RO~P/ZO!^!mi!o!mi!v!mi!y!mi#O!mi#R!mi#[!mi$j!mi$y!mi$t!mi~P!PO$r$TO!^!mi!o!mi!v!mi!y!mi#O!mi#R!mi#[!mi$j!mi$y!mi$t!mi~O{#VO!X#[O~O$m$YO~O}oO#cwO~O#k#hO~P/ZO#k#hO#p$eO#r$fO~O!S$nO!qqO$t$mO~P%`OT$pO#v$pO~O$r$rO!^!wi!o!wi!v!wi!y!wi#O!wi#R!wi#[!wi$j!wi$y!wi$t!wi~O#V$tO#X$uO!^#SX!o#SX!v#SX!y#SX#O#SX#R#SX#[#SX$j#SX$r#SX$y#SX$t#SX~O$r$wO!^#Pi!o#Pi!v#Pi!y#Pi#O#Pi#R#Pi#[#Pi$j#Pi$y#Pi$t#Pi~O#T$yO~OT$zO_!pO}oO!PxO!QxO#T!RO#cwO#hyO#u{O#v!RO#y}O#{!OO$Q!SO$T!VO$V!WO~O$m%TO~OT%UO_!pO}oO!PxO!QxO#T!RO#cwO#hyO#u{O#v!RO#y}O#{!OO$Q!SO$T!VO$V!WO~Oh%YO_!ba!^!ba!d!ba!f!ba!h!ba!j!ba!l!ba!o!ba!v!ba!y!ba#O!ba#R!ba#[!ba$j!ba$y!ba$t!ba~O$r%^O$t%_O~P/ZO$o!zO~O!^!mq!o!mq!v!mq!y!mq#O!mq#R!mq#[!mq$j!mq$y!mq$t!mq~P!PO$r%aO!^!mq!o!mq!v!mq!y!mq#O!mq#R!mq#[!mq$j!mq$y!mq$t!mq~O$m%bO~Ok#WOt#WOu#WOv#WOw#WOx#WOy#WO{#VO}oO!P#YO!Q#YO!S#ZO!T#ZO!U#ZO!X#[O#a#^OTpiepiopi!^pi!opi!vpi!ypi#Opi#Rpi#[pi$jpi$rpi$ypi$tpi#kpi#mpi#rpi_pi!dpi!fpi!hpi!jpi!lpi#ppi#Tpi#cpi#hpi#upi#vpi#ypi#{pi$Qpi$Tpi$Vpi~Or#UO~PC}Orpi~PC}O!S#ZO!T#ZO!U#ZOTpiepikpiopirpitpiupivpiwpixpiypi{pi}pi!Xpi!^pi!opi!vpi!ypi#Opi#Rpi#[pi#api$jpi$rpi$ypi$tpi#kpi#mpi#rpi_pi!dpi!fpi!hpi!jpi!lpi#ppi#Tpi#cpi#hpi#upi#vpi#ypi#{pi$Qpi$Tpi$Vpi~O!P#YO!Q#YO~PGkO!Ppi!Qpi~PGkO#m%iO~P/ZO$t%kO~O$t%lO~P/ZO$t%lO~O!^QO~O!^!wq!o!wq!v!wq!y!wq#O!wq#R!wq#[!wq$j!wq$y!wq$t!wq~P!PO$r%oO!^!wq!o!wq!v!wq!y!wq#O!wq#R!wq#[!wq$j!wq$y!wq$t!wq~O!^#Pq!o#Pq!v#Pq!y#Pq#O#Pq#R#Pq#[#Pq$j#Pq$y#Pq$t#Pq~OT#qO#T#qO~PMbO$r%qO~PMbOT%sO$ZfX~P*TOa%uOc%vO~OT%xO~O$r&SOT$aX_$aX}$aX!P$aX!Q$aX#T$aX#c$aX#h$aX#u$aX#v$aX#y$aX#{$aX$Q$aX$T$aX$V$aX$t$aX~O$t&UO~P>rO$Z&VO~O$r&XOT$iX_$iX}$iX!P$iX!Q$iX#T$iX#c$iX#h$iX#u$iX#v$iX#y$iX#{$iX$Q$iX$T$iX$V$iX$t$iX~O$t&ZO~P?xOh%YO_!bi!^!bi!d!bi!f!bi!h!bi!j!bi!l!bi!o!bi!v!bi!y!bi#O!bi#R!bi#[!bi$j!bi$y!bi$t!bi~O$r&_O$t&`O~O$t&`O~P%`O!^!my!o!my!v!my!y!my#O!my#R!my#[!my$j!my$y!my$t!my~P!PO$r&dO$t!YX~P/ZO$t&eO~O#r#na~P/ZO$t&hO~P/ZO!^!wy!o!wy!v!wy!y!wy#O!wy#R!wy#[!wy$j!wy$y!wy$t!wy~P!POT#qO#T#qO!^#Py!o#Py!v#Py!y#Py#O#Py#R#Py#[#Py$j#Py$y#Py$t#Py~OZ&kOTVa_Va}Va!PVa!QVa#TVa#cVa#hVa#uVa#vVa#yVa#{Va$QVa$TVa$VVa$rVa$tVa~Oe!iOh%YO~O$m&qO~O$t&{O~O_!bq!^!bq!d!bq!f!bq!h!bq!j!bq!l!bq!o!bq!v!bq!y!bq#O!bq#R!bq#[!bq$j!bq$y!bq$t!bq~P/ZO$t'OO~P%`O$r$ca$t$ca~P/ZO$t'PO~O$r'QO$t!Ya~O$t!Ya~P%`O#k#iq#p#iq#r#iq~P/ZO$t'RO~P/ZOT'SO~O$m'WO~Ok#WOt#WOu#WOv#WOw#WOx#WOy#WO{#VO}oO!P#YO!Q#YO!S#ZO!T#ZO!U#ZO!X#[O#a#^Oemiomi~Or#UO~P!,]Ormi~P!,]O!S#ZO!T#ZO!U#ZOemikmiomirmitmiumivmiwmixmiymi{mi}mi!Xmi#ami~O!P#YO!Q#YO~P!-uO!Pmi!Qmi~P!-uOTli_li#Tli#cli#hli#uli#vli#yli#{li$Qli$Tli$Vli$rli$tli~P/ZO_!by!^!by!d!by!f!by!h!by!j!by!l!by!o!by!v!by!y!by#O!by#R!by#[!by$j!by$y!by$t!by~P/ZO$t!Yi~P%`O$o!zOkjXTjX_jX}jX!PjX!QjX#TjX#cjX#hjX#ujX#vjX#yjX#{jX$QjX$TjX$VjX$rjX$tjX~Ok'ZO~O$t'^O~O$t'aO~OP!T!S!Q~",
|
|
1274
|
+
goto: "Hi${PPP$|%QPP%U%Y%]%`P%c%k%qP&OP&OP&RP&e'TP'a'gP%c(q(wP)_*^P*vPPPPPP+bP,OP-sPP.aPPP)_/PP/q/}0i0vP1W1W1]2a2eP2eP2eP2eP2eP0i2iP2vP2|3_0i3jP0i3wP4UP0i4[P0i4iP4vP5OP5OP0i5RP5`P)_5cP5}P7^8a7^9dP:g:mP:sP:v:|P;QP7^;[PP<_=bP=bP<_>e>e?hP7^@kPAnP1b(q(qP$|$|AqPAuA{BRC_CiCxDOD^DdDnPPPPDtPDxEOPGVPG`7^>e)_PHeTcOdT`OdT%P#u%RR%t$zR&m%tR&l%tS%P#u%RT%V#w%XX$|#u#w%R%XS!vl!yQ#|!wX${#u#w%R%XR%w${Q!lhQ!ojQ#fvQ#v!bQ&R$}R&o%xQ!khQ!njQ#evQ#x!lQ#z!oQ$b#fW%S#u#w%R%XQ&y&RR'V&oQ%Z#{Q&]%[Q&n%xR'['VQ'U&nR'`'[#Q!UTfgpuz!Z!^!{#Q#`#a#b#c#d#k#m#u#w$T$Y$i$o$r%R%X%Z%^%`%a%b%j%n%o%|%}&O&P&Q&V&]&_&d&q&}'Q'WS'T&n'[R'_'ZX%O#u#w%R%Xr#`v!g!h#P#g$O$j$l%c%h%m&[&a&g&i&z&|R%|$}!y!YTfgpuz!Z!^!{#Q#`#a#b#c#d#k#m$T$Y$i$o$r%Z%^%`%a%b%j%n%o%|%}&O&P&Q&V&]&_&d&q&}'Q'Wv#av!g!h#P#g$O$]$j$l%c%h%m&[&a&g&i&t&z&|R%}$}z#bv!g!h#P#g$O$]$^$j$l%c%h%m&[&a&g&i&t&u&z&|R&O$}|#Wv!g!h#P#g$O$]$^$j$l$}%c%h%m&[&a&g&i&t&u&z&|T$W#X%y#QxTfgpuz!Z!^!{#Q#`#a#b#c#d#k#m#u#w$T$Y$i$o$r%R%X%Z%^%`%a%b%j%n%o%|%}&O&P&Q&V&]&_&d&q&}'Q'Wz#Xv!g!h#P#g$O$]$^$j$l%c%h%m&[&a&g&i&t&u&z&|Q$[#_Q%y$}R&s%{!O#cv!g!h#P#g$O$]$^$_$j$l%c%h%m&[&a&g&i&t&u&v&z&|R&P$}!S#dv!g!h#P#g$O$]$^$_$`$j$l%c%h%m&[&a&g&i&t&u&v&w&z&|R&Q$}z#]v!g!h#P#g$O$]$^$j$l%c%h%m&[&a&g&i&t&u&z&|Q$X#XQ%z$}R&p%yQ%d$YQ&b%bQ'X&qR']'WSeOdS!mipQ$k#lQ%d$YQ&W%TQ&b%bQ'X&qR']'Wg^O_dip#l$Y%T%b&q'WfRO_dip#l$Y%T%b&q'WR%n$qVkR!v#|UjR!v#|!y!XTfgpuz!Z!^!{#Q#`#a#b#c#d#k#m$T$Y$i$o$r%Z%^%`%a%b%j%n%o%|%}&O&P&Q&V&]&_&d&q&}'Q'WT!xl!yT!wl!ygTO_dip#l$Y%T%b&q'WQuTR$o#mQtTQ#SuQ#p!^]$S#Q$T$r%`%a%ocsTu!^#Q$T$r%`%a%ogfO_dip#l$Y%T%b&q'WgWO_dip#l$Y%T%b&q'WQ!^WR!_ZggO_dip#l$Y%T%b&q'WgZO_dip#l$Y%T%b&q'WQ#r!_V%p$w%q&jR$v#qg]O_dip#l$Y%T%b&q'WR#t!`z#_v!g!h#P#g$O$]$^$j$l%c%h%m&[&a&g&i&t&u&z&|R%{$}#Q!QTfgpuz!Z!^!{#Q#`#a#b#c#d#k#m#u#w$T$Y$i$o$r%R%X%Z%^%`%a%b%j%n%o%|%}&O&P&Q&V&]&_&d&q&}'Q'WQ$Z#_Q%e$[Q&r%{R'Y&s#R!WTfgpuz!Z!^!{#Q#`#a#b#c#d#k#m#u#w$T$Y$i$o$r%R%X%Z%^%`%a%b%j%n%o%|%}&O&P&Q&V&]&_&d&q&}'Q'W#R!ZTfgpuz!Z!^!{#Q#`#a#b#c#d#k#m#u#w$T$Y$i$o$r%R%X%Z%^%`%a%b%j%n%o%|%}&O&P&Q&V&]&_&d&q&}'Q'W#RzTfgpuz!Z!^!{#Q#`#a#b#c#d#k#m#u#w$T$Y$i$o$r%R%X%Z%^%`%a%b%j%n%o%|%}&O&P&Q&V&]&_&d&q&}'Q'WX#iz#g#j$cX#kz#g#j$cR%j$jQ$h#jR%g$cT$i#j$cQ$g#jS%f$c$hR&f%g#R|Tfgpuz!Z!^!{#Q#`#a#b#c#d#k#m#u#w$T$Y$i$o$r%R%X%Z%^%`%a%b%j%n%o%|%}&O&P&Q&V&]&_&d&q&}'Q'W#R!RTfgpuz!Z!^!{#Q#`#a#b#c#d#k#m#u#w$T$Y$i$o$r%R%X%Z%^%`%a%b%j%n%o%|%}&O&P&Q&V&]&_&d&q&}'Q'W#R!PTfgpuz!Z!^!{#Q#`#a#b#c#d#k#m#u#w$T$Y$i$o$r%R%X%Z%^%`%a%b%j%n%o%|%}&O&P&Q&V&]&_&d&q&}'Q'W#R!UTfgpuz!Z!^!{#Q#`#a#b#c#d#k#m#u#w$T$Y$i$o$r%R%X%Z%^%`%a%b%j%n%o%|%}&O&P&Q&V&]&_&d&q&}'Q'W#R!TTfgpuz!Z!^!{#Q#`#a#b#c#d#k#m#u#w$T$Y$i$o$r%R%X%Z%^%`%a%b%j%n%o%|%}&O&P&Q&V&]&_&d&q&}'Q'W#R![Tfgpuz!Z!^!{#Q#`#a#b#c#d#k#m#u#w$T$Y$i$o$r%R%X%Z%^%`%a%b%j%n%o%|%}&O&P&Q&V&]&_&d&q&}'Q'WR$q#oTbOdQdOR!edQ%R#uR&T%RbnTu!^#Q$T$r%`%a%o!t!ffgpz!Z!{#`#a#b#c#d#k#m#u#w$Y$i$o%R%X%Z%^%b%j%n%|%}&O&P&Q&V&]&_&d&n&q&}'Q'W'Z'[T!}n!fQ%]$OS&^%]&cR&c%cd_Odip#l$Y%T%b&q'WR!a_Q!ylR#}!yQ#RtU$U#R$V$sQ$V#SR$s#pQ$x#rR%r$xQ#jzQ$c#gT$d#j$cQ%X#wR&Y%XT%Q#u%RX$}#u#w%R%XbvTu!^#Q$T$r%`%a%oQ!gfQ!hgQ#PpQ#gzQ#n!ZQ$O!{Q$]#`Q$^#aQ$_#bQ$`#cQ$a#dQ$j#kQ$l#mW%c$Y%b&q'WQ%h$iQ%m$oQ&[%ZY&a%^&_&d&}'QQ&g%jQ&i%nQ&t%|Q&u%}Q&v&OQ&w&PQ&x&QQ&z&VR&|&]QlRQ#{!vR%[#|!x!YTfgpuz!Z!^!{#Q#`#a#b#c#d#k#m$T$Y$i$o$r%Z%^%`%a%b%j%n%o%|%}&O&P&Q&V&]&_&d&q&}'Q'WX%O#u#w%R%XT%W#w%X",
|
|
1275
|
+
nodeNames: "\u26A0 Comment Program TableStatement Kw Identifier table ColumnDef DataType PrimaryKey Kw primary_key JoinDef JoinType Kw join Kw one Kw many Kw as Alias Kw on BinaryExpression Ref = ComputedDef BinaryExpression Kw or BinaryExpression Kw and ComparisonOp != <> < > <= >= Kw like Kw not AddOp + - MulOp * / % InExpression Kw in InValueList QueryStatement FromClause Kw from TableName Subquery SubqueryExpression JoinClause Kw inner Kw left Kw right Kw full Kw cross SelectClause Kw select Kw distinct SelectItem Wildcard WhereClause Kw where GroupByClause Kw group Kw by HavingClause Kw having OrderByClause Kw order OrderItem Number Kw asc Kw desc LimitClause Kw limit Kw offset NullTestExpression Kw is Kw null UnaryExpression UnaryOperator CaseExpression Kw case WhenClause Kw when Kw then ElseClause Kw else Kw end ExistsExpression Kw exists String Boolean Kw true Kw false Null FunctionCall Count Kw count ExtractExpression Kw extract ExtractUnit Param Parenthetical InExpression NullTestExpression : ViewStatement ExtendStatement Kw extend",
|
|
1276
|
+
maxTerm: 180,
|
|
1020
1277
|
nodeProps: [
|
|
1021
|
-
["group", -
|
|
1278
|
+
["group", -9, 25, 29, 32, 53, 63, 107, 146, 147, 148, "Expression", -12, 26, 97, 112, 114, 127, 130, 131, 136, 137, 138, 141, 145, "Expression Expression", -2, 61, 62, "TablePrimary"]
|
|
1022
1279
|
],
|
|
1023
1280
|
skippedNodes: [0, 1],
|
|
1024
|
-
repeatNodeCount:
|
|
1025
|
-
tokenData: "*
|
|
1281
|
+
repeatNodeCount: 10,
|
|
1282
|
+
tokenData: "*o~RoX^#Spq#Sqr#wrs$Stu$quv%cwx%hxy&Qyz&Vz{&[{|&a|}&f}!O&k!O!P'[!P!Q'a!Q![(p![!])Z!]!^)`!^!_)e!_!`)z!`!a*P!c!}*^#T#o*^#y#z#S$f$g#S#BY#BZ#S$IS$I_#S$I|$JO#S$JT$JU#S$KV$KW#S&FU&FV#S~#XY$l~X^#Spq#S#y#z#S$f$g#S#BY#BZ#S$IS$I_#S$I|$JO#S$JT$JU#S$KV$KW#S&FU&FV#S~#zP!_!`#}~$SOt~~$VTOr$Srs$fs;'S$S;'S;=`$k<%lO$S~$kO#v~~$nP;=`<%l$S~$tS!Q![%Q!c!}%Q#R#S%Q#T#o%Q~%VS$V~!Q![%Q!c!}%Q#R#S%Q#T#o%Q~%hO!U~~%kTOw%hwx$fx;'S%h;'S;=`%z<%lO%h~%}P;=`<%l%h~&VO$m~~&[O$t~~&aO!S~~&fO!P~~&kO$r~~&pP!Q~}!O&s~&xSP~OY&sZ;'S&s;'S;=`'U<%lO&s~'XP;=`<%l&s~'aO$o~~'fP!T~z{'i~'lTOz'iz{'{{;'S'i;'S;=`(j<%lO'i~(OVOz'iz{'{{!P'i!P!Q(e!Q;'S'i;'S;=`(j<%lO'i~(jOP~~(mP;=`<%l'i~(uQ#T~!O!P({!Q![(p~)OP!Q![)R~)WP#T~!Q![)R~)`O$Z~~)eO$y~~)jQv~!_!`)p!`!a)u~)uOx~~)zOu~~*POk~~*UPw~!_!`*X~*^Oy~~*cST~!Q![*^!c!}*^#R#S*^#T#o*^",
|
|
1026
1283
|
tokenizers: [0],
|
|
1027
1284
|
topRules: { "Program": [0, 2] },
|
|
1028
1285
|
specialized: [{ term: 5, get: (value, stack) => specializeIdentifier(value, stack) << 1, external: specializeIdentifier }, { term: 5, get: (value) => spec_Identifier[value] || -1 }],
|
|
1029
|
-
tokenPrec:
|
|
1286
|
+
tokenPrec: 2957
|
|
1030
1287
|
});
|
|
1031
1288
|
}
|
|
1032
1289
|
});
|
|
@@ -1189,6 +1446,7 @@ function isFence(event) {
|
|
|
1189
1446
|
var COMPONENT_ATTRIBUTE_KEYS, GSQL_FENCE, COMPONENT_TAG, ATTRIBUTE;
|
|
1190
1447
|
var init_markdown = __esm({
|
|
1191
1448
|
"../lang/markdown.ts"() {
|
|
1449
|
+
"use strict";
|
|
1192
1450
|
init_parser();
|
|
1193
1451
|
COMPONENT_ATTRIBUTE_KEYS = ["x", "y", "y2", "series", "value", "category"];
|
|
1194
1452
|
GSQL_FENCE = /^([ \t]*)(`{3,})g?sql[^\n]*\n([\s\S]*?)^\1\2[ \t]*$/gim;
|
|
@@ -1197,6 +1455,74 @@ var init_markdown = __esm({
|
|
|
1197
1455
|
}
|
|
1198
1456
|
});
|
|
1199
1457
|
|
|
1458
|
+
// ../lang/snowflake.ts
|
|
1459
|
+
function uppercaseMalloyQuery(query) {
|
|
1460
|
+
query.baseTableName = uppercaseIdentifier(query.baseTableName);
|
|
1461
|
+
for (let stage of query.pipeline || []) {
|
|
1462
|
+
let fields = stage.queryFields || [];
|
|
1463
|
+
fields.forEach((field) => uppercaseColumnField(field));
|
|
1464
|
+
let filters = stage.filterList || [];
|
|
1465
|
+
filters.forEach((filter) => uppercaseExpression(filter?.e));
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
function uppercaseTable(table2) {
|
|
1469
|
+
if (table2.upperCased) return;
|
|
1470
|
+
table2.upperCased = true;
|
|
1471
|
+
table2.name = uppercaseIdentifier(table2.name);
|
|
1472
|
+
if (table2.primaryKey) table2.primaryKey = uppercaseIdentifier(table2.primaryKey);
|
|
1473
|
+
if (table2.tableName) table2.tableName = uppercaseQualified(table2.tableName);
|
|
1474
|
+
if (table2.tablePath) table2.tablePath = uppercaseQualified(table2.tablePath);
|
|
1475
|
+
table2.fields?.forEach((field) => uppercaseField(field));
|
|
1476
|
+
if (table2.query) uppercaseMalloyQuery(table2.query);
|
|
1477
|
+
}
|
|
1478
|
+
function uppercaseField(field) {
|
|
1479
|
+
if (!field) return;
|
|
1480
|
+
if (isJoinField(field)) {
|
|
1481
|
+
field.name = uppercaseIdentifier(field.name);
|
|
1482
|
+
if (field.tableName) field.tableName = uppercaseQualified(field.tableName);
|
|
1483
|
+
if (field.tablePath) field.tablePath = uppercaseQualified(field.tablePath);
|
|
1484
|
+
if (field.structPath) field.structPath = field.structPath.map(uppercaseIdentifier);
|
|
1485
|
+
if (field.path) field.path = field.path.map(uppercaseIdentifier);
|
|
1486
|
+
if (field.onExpression) uppercaseExpression(field.onExpression);
|
|
1487
|
+
uppercaseTable(field);
|
|
1488
|
+
} else {
|
|
1489
|
+
uppercaseColumnField(field);
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
function uppercaseColumnField(field) {
|
|
1493
|
+
if (!field) return;
|
|
1494
|
+
field.name = uppercaseIdentifier(field.name);
|
|
1495
|
+
if (field.path) field.path = field.path.map(uppercaseIdentifier);
|
|
1496
|
+
if (field.structPath) field.structPath = field.structPath.map(uppercaseIdentifier);
|
|
1497
|
+
if (field.tableName) field.tableName = uppercaseQualified(field.tableName);
|
|
1498
|
+
if (field.tablePath) field.tablePath = uppercaseQualified(field.tablePath);
|
|
1499
|
+
if (field.e) uppercaseExpression(field.e);
|
|
1500
|
+
}
|
|
1501
|
+
function uppercaseExpression(expr) {
|
|
1502
|
+
if (!expr) return;
|
|
1503
|
+
walkExpression(expr, (node) => {
|
|
1504
|
+
if (Array.isArray(node.path)) node.path = node.path.map(uppercaseIdentifier);
|
|
1505
|
+
if (Array.isArray(node.structPath)) node.structPath = node.structPath.map(uppercaseIdentifier);
|
|
1506
|
+
});
|
|
1507
|
+
}
|
|
1508
|
+
function uppercaseIdentifier(value) {
|
|
1509
|
+
if (!value) return value || "";
|
|
1510
|
+
return value.toString().toUpperCase();
|
|
1511
|
+
}
|
|
1512
|
+
function uppercaseQualified(value) {
|
|
1513
|
+
if (!value) return value;
|
|
1514
|
+
return value.split(".").map((part) => part.startsWith('"') && part.endsWith('"') ? part : uppercaseIdentifier(part)).join(".");
|
|
1515
|
+
}
|
|
1516
|
+
function isJoinField(field) {
|
|
1517
|
+
return !!field?.join;
|
|
1518
|
+
}
|
|
1519
|
+
var init_snowflake = __esm({
|
|
1520
|
+
"../lang/snowflake.ts"() {
|
|
1521
|
+
"use strict";
|
|
1522
|
+
init_util();
|
|
1523
|
+
}
|
|
1524
|
+
});
|
|
1525
|
+
|
|
1200
1526
|
// ../lang/core.ts
|
|
1201
1527
|
import { registerDialect, StandardSQLDialect, QueryModel, expandBlueprintMap } from "@graphenedata/malloy";
|
|
1202
1528
|
import { readFile } from "node:fs/promises";
|
|
@@ -1211,15 +1537,19 @@ function getDiagnostics() {
|
|
|
1211
1537
|
async function loadWorkspace(dir, includeMd) {
|
|
1212
1538
|
let files = await glob(includeMd ? "**/*.{gsql,md}" : "**/*.gsql", { cwd: dir, ignore: ["node_modules/**"] });
|
|
1213
1539
|
for await (let file of files) {
|
|
1214
|
-
|
|
1215
|
-
|
|
1540
|
+
try {
|
|
1541
|
+
let contents = await readFile(path2.join(dir, file), "utf-8");
|
|
1542
|
+
updateFile(contents, file);
|
|
1543
|
+
} catch (e) {
|
|
1544
|
+
console.error("Failed to read file", file, e.message);
|
|
1545
|
+
}
|
|
1216
1546
|
}
|
|
1217
1547
|
}
|
|
1218
|
-
function updateFile(contents,
|
|
1219
|
-
FILE_MAP[
|
|
1220
|
-
FILE_MAP[
|
|
1221
|
-
FILE_MAP[
|
|
1222
|
-
return FILE_MAP[
|
|
1548
|
+
function updateFile(contents, path10) {
|
|
1549
|
+
FILE_MAP[path10] ||= { path: path10, contents, tree: null, tables: [], queries: [] };
|
|
1550
|
+
FILE_MAP[path10].contents = contents;
|
|
1551
|
+
FILE_MAP[path10].tree = null;
|
|
1552
|
+
return FILE_MAP[path10];
|
|
1223
1553
|
}
|
|
1224
1554
|
function analyze(contents, type) {
|
|
1225
1555
|
clearDiagnostics();
|
|
@@ -1232,6 +1562,7 @@ function analyze(contents, type) {
|
|
|
1232
1562
|
recordSyntaxErrors(fi);
|
|
1233
1563
|
findTables(fi);
|
|
1234
1564
|
});
|
|
1565
|
+
Object.values(FILE_MAP).forEach(applyExtends);
|
|
1235
1566
|
Object.values(FILE_MAP).flatMap((f) => f.tables).forEach(analyzeTable);
|
|
1236
1567
|
if (contents) {
|
|
1237
1568
|
let fi = FILE_MAP["input"];
|
|
@@ -1243,12 +1574,22 @@ function analyze(contents, type) {
|
|
|
1243
1574
|
}
|
|
1244
1575
|
}
|
|
1245
1576
|
function toSql(query, params = {}) {
|
|
1246
|
-
|
|
1247
|
-
let
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1577
|
+
if (query.rawSql) return query.rawSql;
|
|
1578
|
+
let contents = Object.fromEntries(Object.values(FILE_MAP).flatMap((fi) => {
|
|
1579
|
+
return fi.tables.map((t) => {
|
|
1580
|
+
t = structuredClone(t);
|
|
1581
|
+
if (fi.path == "input" && t.query) fillInParams(t.query, params);
|
|
1582
|
+
if (config.dialect == "snowflake") uppercaseTable(t);
|
|
1583
|
+
return [t.name, t];
|
|
1584
|
+
});
|
|
1585
|
+
}));
|
|
1586
|
+
query = structuredClone(query);
|
|
1587
|
+
fillInParams(query, params);
|
|
1588
|
+
if (config.dialect == "snowflake") uppercaseMalloyQuery(query);
|
|
1589
|
+
let tableQueries = Object.values(contents).map((t) => t.query);
|
|
1590
|
+
let joinQueries = Object.values(contents).flatMap((t) => t.fields.map((f) => f.query));
|
|
1591
|
+
let allQueries = [...tableQueries, ...joinQueries, query].filter((q) => !!q);
|
|
1592
|
+
allQueries.forEach((q) => q.structRef = contents[q.baseTableName]);
|
|
1252
1593
|
let qm = new QueryModel({
|
|
1253
1594
|
name: "generated_model",
|
|
1254
1595
|
contents,
|
|
@@ -1256,11 +1597,12 @@ function toSql(query, params = {}) {
|
|
|
1256
1597
|
dependencies: {},
|
|
1257
1598
|
exports: []
|
|
1258
1599
|
});
|
|
1259
|
-
return qm.compileQuery(
|
|
1600
|
+
return qm.compileQuery(query).sql;
|
|
1260
1601
|
}
|
|
1261
1602
|
var BigQueryDialect;
|
|
1262
1603
|
var init_core = __esm({
|
|
1263
1604
|
"../lang/core.ts"() {
|
|
1605
|
+
"use strict";
|
|
1264
1606
|
init_analyze();
|
|
1265
1607
|
init_params();
|
|
1266
1608
|
init_util();
|
|
@@ -1268,6 +1610,7 @@ var init_core = __esm({
|
|
|
1268
1610
|
init_functions();
|
|
1269
1611
|
init_parser();
|
|
1270
1612
|
init_markdown();
|
|
1613
|
+
init_snowflake();
|
|
1271
1614
|
BigQueryDialect = class extends StandardSQLDialect {
|
|
1272
1615
|
constructor() {
|
|
1273
1616
|
super();
|
|
@@ -1281,6 +1624,543 @@ var init_core = __esm({
|
|
|
1281
1624
|
}
|
|
1282
1625
|
});
|
|
1283
1626
|
|
|
1627
|
+
// printer.ts
|
|
1628
|
+
import { styleText as nodeStyleText } from "node:util";
|
|
1629
|
+
import Table from "cli-table3";
|
|
1630
|
+
import chalk from "chalk";
|
|
1631
|
+
function offsetToLineCol(src, offset2) {
|
|
1632
|
+
let lines = src.split(/\r?\n/);
|
|
1633
|
+
let acc = 0;
|
|
1634
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1635
|
+
let lineText = lines[i];
|
|
1636
|
+
let nextAcc = acc + lineText.length + 1;
|
|
1637
|
+
if (offset2 < nextAcc || i === lines.length - 1) {
|
|
1638
|
+
let col = Math.max(0, offset2 - acc);
|
|
1639
|
+
return { line: i + 1, col, lineStart: acc, lineText };
|
|
1640
|
+
}
|
|
1641
|
+
acc = nextAcc;
|
|
1642
|
+
}
|
|
1643
|
+
return { line: 1, col: 0, lineStart: 0, lineText: lines[0] || "" };
|
|
1644
|
+
}
|
|
1645
|
+
function printDiagnostics(diags, log) {
|
|
1646
|
+
log ||= console.log;
|
|
1647
|
+
let parts = [];
|
|
1648
|
+
for (let d of diags) {
|
|
1649
|
+
let src = getFile2(d.file)?.contents || "";
|
|
1650
|
+
let { line, col, lineStart, lineText } = offsetToLineCol(src, d.from.offset);
|
|
1651
|
+
let endCol = Math.max(col + 1, Math.min(lineText.length, d.to.offset - lineStart));
|
|
1652
|
+
let caretLen = Math.max(1, endCol - col);
|
|
1653
|
+
let sev = d.severity === "error" ? "red" : "yellow";
|
|
1654
|
+
let header = `${styleText(sev, d.severity.toUpperCase())}: ${d.file} line ${line}: ${d.message}`;
|
|
1655
|
+
let gutter = " | ";
|
|
1656
|
+
let caretLine = `${" ".repeat(col)}${styleText(sev, "^".repeat(caretLen))}`;
|
|
1657
|
+
parts.push([header, `${gutter}${lineText}`, `${gutter}${caretLine}`].join("\n"));
|
|
1658
|
+
}
|
|
1659
|
+
if (parts.length) log(parts.join("\n"));
|
|
1660
|
+
}
|
|
1661
|
+
function printTable(rows) {
|
|
1662
|
+
if (!rows || rows.length === 0) {
|
|
1663
|
+
console.log(chalk.yellow("No results returned"));
|
|
1664
|
+
return;
|
|
1665
|
+
}
|
|
1666
|
+
let headers = Object.keys(rows[0]);
|
|
1667
|
+
let table2 = new Table({ head: headers.map((h) => chalk.blue(h)) });
|
|
1668
|
+
let MAX_DISPLAY_ROWS = 200;
|
|
1669
|
+
let displayRows = rows.slice(0, MAX_DISPLAY_ROWS);
|
|
1670
|
+
displayRows.forEach((row) => table2.push(headers.map((h) => row[h]?.toString() || "")));
|
|
1671
|
+
console.log(table2.toString());
|
|
1672
|
+
if (rows.length > MAX_DISPLAY_ROWS) {
|
|
1673
|
+
console.log(chalk.yellow(`Displayed first ${MAX_DISPLAY_ROWS} rows (of ${rows.length} total).`));
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
var styleText;
|
|
1677
|
+
var init_printer = __esm({
|
|
1678
|
+
"printer.ts"() {
|
|
1679
|
+
"use strict";
|
|
1680
|
+
init_core();
|
|
1681
|
+
styleText = (style, text) => {
|
|
1682
|
+
try {
|
|
1683
|
+
return nodeStyleText ? nodeStyleText(style, text) : text;
|
|
1684
|
+
} catch {
|
|
1685
|
+
return text;
|
|
1686
|
+
}
|
|
1687
|
+
};
|
|
1688
|
+
}
|
|
1689
|
+
});
|
|
1690
|
+
|
|
1691
|
+
// background.ts
|
|
1692
|
+
import { spawn, exec } from "child_process";
|
|
1693
|
+
import { promisify } from "util";
|
|
1694
|
+
import { fileURLToPath } from "url";
|
|
1695
|
+
import fs2 from "fs-extra";
|
|
1696
|
+
import path3 from "path";
|
|
1697
|
+
async function runServeInBackground() {
|
|
1698
|
+
let grapheneCache = getGrapheneCache(config.root);
|
|
1699
|
+
let logFile = path3.join(grapheneCache, "serve.log");
|
|
1700
|
+
await fs2.ensureDir(grapheneCache);
|
|
1701
|
+
let log = fs2.openSync(logFile, "w");
|
|
1702
|
+
let entryPoint = process.argv[1] || fileURLToPath(import.meta.url);
|
|
1703
|
+
let childArgs = [...process.execArgv, entryPoint, "serve"];
|
|
1704
|
+
let child = spawn(process.execPath, childArgs, {
|
|
1705
|
+
cwd: config.root,
|
|
1706
|
+
detached: true,
|
|
1707
|
+
env: { ...process.env },
|
|
1708
|
+
stdio: ["ignore", log, log]
|
|
1709
|
+
});
|
|
1710
|
+
if (!child.pid) throw new Error("Failed to start server process");
|
|
1711
|
+
await new Promise((resolve, reject) => {
|
|
1712
|
+
let buffer = "";
|
|
1713
|
+
fs2.watchFile(logFile, { interval: 200 }, (curr, prev) => {
|
|
1714
|
+
if (curr.size > prev.size) {
|
|
1715
|
+
let stream = fs2.createReadStream(logFile, { start: 0, end: curr.size - 1 });
|
|
1716
|
+
stream.on("data", (d) => {
|
|
1717
|
+
process.stdout.write(d);
|
|
1718
|
+
buffer = (buffer + d.toString()).slice(-200);
|
|
1719
|
+
if (buffer.includes("Server running at http://localhost:")) resolve();
|
|
1720
|
+
});
|
|
1721
|
+
}
|
|
1722
|
+
});
|
|
1723
|
+
child.once("exit", () => {
|
|
1724
|
+
reject(new Error("Exited before server started"));
|
|
1725
|
+
});
|
|
1726
|
+
child.once("error", (e) => reject(e));
|
|
1727
|
+
});
|
|
1728
|
+
}
|
|
1729
|
+
function getGrapheneCache(root) {
|
|
1730
|
+
return path3.join(root, "node_modules", ".graphene");
|
|
1731
|
+
}
|
|
1732
|
+
function sendSignal(pid, signal) {
|
|
1733
|
+
let pids = process.platform === "win32" ? [pid] : [pid, -pid];
|
|
1734
|
+
for (let target of pids) {
|
|
1735
|
+
try {
|
|
1736
|
+
process.kill(target, signal);
|
|
1737
|
+
} catch (err) {
|
|
1738
|
+
let code = err.code;
|
|
1739
|
+
if (code === "ESRCH" || code === "EINVAL") continue;
|
|
1740
|
+
return false;
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
return true;
|
|
1744
|
+
}
|
|
1745
|
+
async function stopGrapheneIfRunning() {
|
|
1746
|
+
let port = Number(process.env.GRAPHENE_PORT) || 4e3;
|
|
1747
|
+
let pid = await getPidOnPort(port);
|
|
1748
|
+
if (!pid) return;
|
|
1749
|
+
console.log(`Stopping server (${pid})`);
|
|
1750
|
+
sendSignal(pid, "SIGTERM");
|
|
1751
|
+
let end = Date.now() + 5e3;
|
|
1752
|
+
while (Date.now() < end) {
|
|
1753
|
+
if (!await getPidOnPort(port)) break;
|
|
1754
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1755
|
+
}
|
|
1756
|
+
if (await getPidOnPort(port)) {
|
|
1757
|
+
sendSignal(pid, "SIGKILL");
|
|
1758
|
+
}
|
|
1759
|
+
if (await getPidOnPort(port)) {
|
|
1760
|
+
console.error("Failed to stop previous Graphene server");
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
async function isServerRunning(portOverride) {
|
|
1764
|
+
let port = portOverride || Number(process.env.GRAPHENE_PORT) || 4e3;
|
|
1765
|
+
return !!await getPidOnPort(port);
|
|
1766
|
+
}
|
|
1767
|
+
async function getPidOnPort(port) {
|
|
1768
|
+
try {
|
|
1769
|
+
if (process.platform === "win32") {
|
|
1770
|
+
let { stdout } = await execAsync(`netstat -ano | findstr :${port}`);
|
|
1771
|
+
let lines = stdout.trim().split("\n");
|
|
1772
|
+
for (let line of lines) {
|
|
1773
|
+
let parts = line.trim().split(/\s+/);
|
|
1774
|
+
if (parts.length < 5) continue;
|
|
1775
|
+
let localAddress = parts[1];
|
|
1776
|
+
let pid = parseInt(parts[parts.length - 1], 10);
|
|
1777
|
+
if (localAddress.endsWith(`:${port}`)) {
|
|
1778
|
+
return pid;
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
} else {
|
|
1782
|
+
return new Promise((resolve) => {
|
|
1783
|
+
let child = spawn("lsof", ["-i", `:${port}`, "-t", "-sTCP:LISTEN"]);
|
|
1784
|
+
let stdout = "";
|
|
1785
|
+
child.stdout.on("data", (d) => stdout += d.toString());
|
|
1786
|
+
child.on("close", (code) => {
|
|
1787
|
+
if (code !== 0) return resolve(void 0);
|
|
1788
|
+
let pid = parseInt(stdout.trim(), 10);
|
|
1789
|
+
resolve(isNaN(pid) ? void 0 : pid);
|
|
1790
|
+
});
|
|
1791
|
+
child.on("error", () => resolve(void 0));
|
|
1792
|
+
});
|
|
1793
|
+
}
|
|
1794
|
+
} catch (e) {
|
|
1795
|
+
console.warn("Failed to check for server:", e.message);
|
|
1796
|
+
return void 0;
|
|
1797
|
+
}
|
|
1798
|
+
return void 0;
|
|
1799
|
+
}
|
|
1800
|
+
var execAsync;
|
|
1801
|
+
var init_background = __esm({
|
|
1802
|
+
"background.ts"() {
|
|
1803
|
+
"use strict";
|
|
1804
|
+
init_config();
|
|
1805
|
+
execAsync = promisify(exec);
|
|
1806
|
+
}
|
|
1807
|
+
});
|
|
1808
|
+
|
|
1809
|
+
// mockFiles.ts
|
|
1810
|
+
var mockFileMap;
|
|
1811
|
+
var init_mockFiles = __esm({
|
|
1812
|
+
"mockFiles.ts"() {
|
|
1813
|
+
"use strict";
|
|
1814
|
+
mockFileMap = {};
|
|
1815
|
+
}
|
|
1816
|
+
});
|
|
1817
|
+
|
|
1818
|
+
// check.ts
|
|
1819
|
+
import fs3 from "fs-extra";
|
|
1820
|
+
import os from "os";
|
|
1821
|
+
import path4 from "path";
|
|
1822
|
+
import { spawn as spawn2 } from "child_process";
|
|
1823
|
+
import { WebSocketServer } from "ws";
|
|
1824
|
+
import { readFileSync as readFileSync2 } from "node:fs";
|
|
1825
|
+
import { styleText as styleText2 } from "node:util";
|
|
1826
|
+
async function check(options) {
|
|
1827
|
+
let log = options.log || console.log;
|
|
1828
|
+
let mdFile = options.mdArg && normalizeMdFile(options.mdArg);
|
|
1829
|
+
if (options.mdArg && !mdFile) {
|
|
1830
|
+
log(`Couldn't find ${options.mdArg}`);
|
|
1831
|
+
return false;
|
|
1832
|
+
}
|
|
1833
|
+
await loadWorkspace(config.root, !mdFile);
|
|
1834
|
+
if (mdFile) {
|
|
1835
|
+
if (process.env.NODE_ENV == "test" && mockFileMap[mdFile]) {
|
|
1836
|
+
updateFile(mockFileMap[mdFile], mdFile);
|
|
1837
|
+
} else {
|
|
1838
|
+
let content = readFileSync2(path4.resolve(config.root, mdFile), "utf-8");
|
|
1839
|
+
updateFile(content, mdFile);
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
analyze();
|
|
1843
|
+
if (getDiagnostics().length > 0) {
|
|
1844
|
+
printDiagnostics(getDiagnostics(), log);
|
|
1845
|
+
return false;
|
|
1846
|
+
}
|
|
1847
|
+
if (!mdFile) {
|
|
1848
|
+
log("No errors found \u{1F48E}");
|
|
1849
|
+
return true;
|
|
1850
|
+
}
|
|
1851
|
+
let host = `http://localhost:${config.port || Number(process.env.GRAPHENE_PORT) || 4e3}`;
|
|
1852
|
+
let pageUrl = "/" + mdFile.replace(/\.md$/, "").replace(/^\//, "").replace(/\\/g, "/");
|
|
1853
|
+
if (pageUrl === "/index") pageUrl = "/";
|
|
1854
|
+
if (process.env.NODE_ENV !== "test" && !await isServerRunning()) {
|
|
1855
|
+
log("Starting Graphene server...");
|
|
1856
|
+
await runServeInBackground();
|
|
1857
|
+
}
|
|
1858
|
+
let resp = await sendCheckRequest({ host, pageUrl, chart: options.chart });
|
|
1859
|
+
if (resp.checkError == "no_server") {
|
|
1860
|
+
log("Failed to start Graphene server");
|
|
1861
|
+
return false;
|
|
1862
|
+
}
|
|
1863
|
+
if (resp.checkError == "no_tab" && process.env.NODE_ENV !== "test") {
|
|
1864
|
+
log(`Opening page ${host}${pageUrl}`);
|
|
1865
|
+
spawn2("open", [host + pageUrl]);
|
|
1866
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1867
|
+
resp = await sendCheckRequest({ host, pageUrl, chart: options.chart });
|
|
1868
|
+
}
|
|
1869
|
+
if (resp.checkError == "no_tab") {
|
|
1870
|
+
log("Failed to open a new tab");
|
|
1871
|
+
return false;
|
|
1872
|
+
}
|
|
1873
|
+
if (resp.checkError) {
|
|
1874
|
+
log("Failed to run check: " + resp.checkError);
|
|
1875
|
+
return false;
|
|
1876
|
+
}
|
|
1877
|
+
let errors = Array.from(resp.errors || []);
|
|
1878
|
+
if (errors.length) {
|
|
1879
|
+
log(styleText2("red", "Runtime errors") + ` in ${mdFile}:`);
|
|
1880
|
+
} else {
|
|
1881
|
+
log("No errors found \u{1F48E}");
|
|
1882
|
+
}
|
|
1883
|
+
errors.forEach((e) => {
|
|
1884
|
+
if (e.file && e.line) printDiagnostics([e], log);
|
|
1885
|
+
else if (e.id) log(`${e.id}: ${e.message}`);
|
|
1886
|
+
else log(e.message);
|
|
1887
|
+
});
|
|
1888
|
+
if (resp?.stillLoading) {
|
|
1889
|
+
log("Warning: Queries were still loading when the screenshot was taken");
|
|
1890
|
+
}
|
|
1891
|
+
if (resp?.screenshot) {
|
|
1892
|
+
let filename = `graphene-screenshot-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.png`;
|
|
1893
|
+
let screenshotPath = path4.join(os.tmpdir(), filename);
|
|
1894
|
+
let base64Data = resp.screenshot.replace(/^data:image\/png;base64,/, "");
|
|
1895
|
+
await fs3.writeFile(screenshotPath, base64Data, "base64");
|
|
1896
|
+
log("Screenshot saved to", screenshotPath);
|
|
1897
|
+
}
|
|
1898
|
+
return errors.length == 0;
|
|
1899
|
+
}
|
|
1900
|
+
async function sendCheckRequest({ host, pageUrl, chart }) {
|
|
1901
|
+
let abort = new AbortController();
|
|
1902
|
+
let timeout = setTimeout(() => abort.abort(), 3e4);
|
|
1903
|
+
try {
|
|
1904
|
+
let response = await fetch(`${host}/_api/check`, {
|
|
1905
|
+
method: "POST",
|
|
1906
|
+
headers: { "Content-Type": "application/json" },
|
|
1907
|
+
body: JSON.stringify({ pageUrl: host + pageUrl, chart }),
|
|
1908
|
+
signal: abort.signal
|
|
1909
|
+
});
|
|
1910
|
+
clearTimeout(timeout);
|
|
1911
|
+
let body = response.headers.get("content-type") == "application/json" ? await response.json() : { error: await response.text() };
|
|
1912
|
+
if (!response.ok) {
|
|
1913
|
+
if (body.error) return { checkError: body.error };
|
|
1914
|
+
console.error(`Unexpected response: ${JSON.stringify(body)}`);
|
|
1915
|
+
return { checkError: "Unexpected response from Graphene server" };
|
|
1916
|
+
}
|
|
1917
|
+
return body;
|
|
1918
|
+
} catch (err) {
|
|
1919
|
+
clearTimeout(timeout);
|
|
1920
|
+
if (err.name === "AbortError") return { checkError: "timeout" };
|
|
1921
|
+
return { checkError: "no_server" };
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
function normalizeMdFile(mdFile) {
|
|
1925
|
+
let clean = mdFile.trim();
|
|
1926
|
+
if (!clean) return null;
|
|
1927
|
+
if (!clean.endsWith(".md")) clean = clean + ".md";
|
|
1928
|
+
if (process.env.NODE_ENV == "test" && mockFileMap[clean]) {
|
|
1929
|
+
return clean;
|
|
1930
|
+
}
|
|
1931
|
+
let absolute = [
|
|
1932
|
+
path4.resolve(process.cwd(), clean),
|
|
1933
|
+
path4.resolve(config.root, clean)
|
|
1934
|
+
].find((p) => fs3.existsSync(p)) || null;
|
|
1935
|
+
if (!absolute) return null;
|
|
1936
|
+
let relative = path4.relative(config.root, absolute);
|
|
1937
|
+
return relative;
|
|
1938
|
+
}
|
|
1939
|
+
async function proxyCheckRequest(req, res) {
|
|
1940
|
+
let chunks = [];
|
|
1941
|
+
for await (let chunk of req) chunks.push(chunk);
|
|
1942
|
+
let { pageUrl, chart } = JSON.parse(Buffer.concat(chunks).toString());
|
|
1943
|
+
let id = Math.random().toString(36).slice(2);
|
|
1944
|
+
res.setHeader("Content-Type", "application/json");
|
|
1945
|
+
let normalizedPageUrl = pageUrl.replace(/\/$/, "");
|
|
1946
|
+
let conn = await pollFor(() => browserConnections.find((conn2) => conn2.url === normalizedPageUrl), 5e3, 100);
|
|
1947
|
+
if (!conn) {
|
|
1948
|
+
res.statusCode = 400;
|
|
1949
|
+
res.end(JSON.stringify({ error: "no_tab" }));
|
|
1950
|
+
return;
|
|
1951
|
+
} else {
|
|
1952
|
+
conn.socket.send(JSON.stringify({ type: "check", chart, requestId: id }));
|
|
1953
|
+
pendingRequests[id] = { response: res };
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1956
|
+
function checkVitePlugin() {
|
|
1957
|
+
return {
|
|
1958
|
+
name: "graphene-check-plugin",
|
|
1959
|
+
configureServer(server) {
|
|
1960
|
+
let wss = new WebSocketServer({ noServer: true });
|
|
1961
|
+
server.httpServer?.on("upgrade", (req, socket, head) => {
|
|
1962
|
+
if (!req.url || !req.url.includes("/_api/ws") && !req.url.includes("graphene-ws")) return;
|
|
1963
|
+
wss.handleUpgrade(req, socket, head, (ws) => {
|
|
1964
|
+
wss.emit("connection", ws, req);
|
|
1965
|
+
});
|
|
1966
|
+
});
|
|
1967
|
+
wss.on("connection", (socket) => {
|
|
1968
|
+
socket.on("message", (data) => {
|
|
1969
|
+
let message = JSON.parse(data.toString());
|
|
1970
|
+
if (message.type === "register") {
|
|
1971
|
+
let normalizedUrl = message.url.replace(/\/$/, "");
|
|
1972
|
+
browserConnections.push({ url: normalizedUrl, socket });
|
|
1973
|
+
}
|
|
1974
|
+
if (message.type === "checkResponse") {
|
|
1975
|
+
pendingRequests[message.requestId].response.end(JSON.stringify(message));
|
|
1976
|
+
delete pendingRequests[message.requestId];
|
|
1977
|
+
}
|
|
1978
|
+
});
|
|
1979
|
+
socket.on("close", () => {
|
|
1980
|
+
browserConnections = browserConnections.filter((conn) => conn.socket !== socket);
|
|
1981
|
+
});
|
|
1982
|
+
});
|
|
1983
|
+
server.httpServer?.on("close", () => wss.close());
|
|
1984
|
+
server.middlewares.use(async (req, res, next) => {
|
|
1985
|
+
let [pathName] = (req.url || "").split("?");
|
|
1986
|
+
if (pathName === "/_api/check") await proxyCheckRequest(req, res);
|
|
1987
|
+
else next();
|
|
1988
|
+
});
|
|
1989
|
+
}
|
|
1990
|
+
};
|
|
1991
|
+
}
|
|
1992
|
+
var browserConnections, pendingRequests;
|
|
1993
|
+
var init_check = __esm({
|
|
1994
|
+
"check.ts"() {
|
|
1995
|
+
"use strict";
|
|
1996
|
+
init_core();
|
|
1997
|
+
init_printer();
|
|
1998
|
+
init_mockFiles();
|
|
1999
|
+
init_background();
|
|
2000
|
+
init_util();
|
|
2001
|
+
browserConnections = [];
|
|
2002
|
+
pendingRequests = {};
|
|
2003
|
+
}
|
|
2004
|
+
});
|
|
2005
|
+
|
|
2006
|
+
// auth.ts
|
|
2007
|
+
import fs4 from "node:fs/promises";
|
|
2008
|
+
import path5 from "path";
|
|
2009
|
+
import os2 from "os";
|
|
2010
|
+
import { spawn as spawn3 } from "child_process";
|
|
2011
|
+
import http from "http";
|
|
2012
|
+
async function readStore() {
|
|
2013
|
+
try {
|
|
2014
|
+
let txt2 = await fs4.readFile(credsPath, "utf8");
|
|
2015
|
+
return JSON.parse(txt2) || {};
|
|
2016
|
+
} catch {
|
|
2017
|
+
return {};
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
async function readEntry() {
|
|
2021
|
+
let store = await readStore();
|
|
2022
|
+
return store[config.root];
|
|
2023
|
+
}
|
|
2024
|
+
async function updateEntry(cred) {
|
|
2025
|
+
let store = await readStore();
|
|
2026
|
+
cred.refresh_token ||= store[config.root]?.refresh_token;
|
|
2027
|
+
cred.expires_at = Date.now() + cred.expires_in;
|
|
2028
|
+
store[config.root] = cred;
|
|
2029
|
+
await fs4.mkdir(path5.dirname(credsPath), { recursive: true, mode: 448 });
|
|
2030
|
+
await fs4.writeFile(credsPath, JSON.stringify(store, null, 2) + "\n", { mode: 384 });
|
|
2031
|
+
if (process.platform !== "win32") {
|
|
2032
|
+
await fs4.chmod(credsPath, 384).catch(() => {
|
|
2033
|
+
});
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
function openInBrowser(url) {
|
|
2037
|
+
try {
|
|
2038
|
+
let plat = process.platform;
|
|
2039
|
+
let cmd = "xdg-open";
|
|
2040
|
+
if (plat == "darwin") cmd = "open";
|
|
2041
|
+
if (plat == "win32") cmd = "start";
|
|
2042
|
+
let p = spawn3(cmd, [url], { stdio: "ignore", shell: plat === "win32" });
|
|
2043
|
+
p.unref();
|
|
2044
|
+
} catch {
|
|
2045
|
+
console.log(`Open this URL to authenticate:
|
|
2046
|
+
${url}`);
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
function base64url(buf) {
|
|
2050
|
+
let b64 = Buffer.from(buf).toString("base64");
|
|
2051
|
+
return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
2052
|
+
}
|
|
2053
|
+
function randomBytes(len = 32) {
|
|
2054
|
+
return crypto.getRandomValues(new Uint8Array(len));
|
|
2055
|
+
}
|
|
2056
|
+
async function startLoopback() {
|
|
2057
|
+
let server = http.createServer();
|
|
2058
|
+
await new Promise((r) => server.listen(0, "127.0.0.1", () => r()));
|
|
2059
|
+
let addr = server.address();
|
|
2060
|
+
if (!addr || typeof addr !== "object") throw new Error("Couldnt start oauth callback server");
|
|
2061
|
+
let redirectBase = `http://localhost:${addr.port}`;
|
|
2062
|
+
let waitForCode = new Promise((resolve) => {
|
|
2063
|
+
server.on("request", (req, res) => {
|
|
2064
|
+
let url = new URL(req.url || "/", redirectBase);
|
|
2065
|
+
if (url.pathname !== "/callback") {
|
|
2066
|
+
res.statusCode = 404;
|
|
2067
|
+
res.end("Not Found");
|
|
2068
|
+
return;
|
|
2069
|
+
}
|
|
2070
|
+
let code = url.searchParams.get("code") || "";
|
|
2071
|
+
let state = url.searchParams.get("state") || "";
|
|
2072
|
+
res.statusCode = 200;
|
|
2073
|
+
res.setHeader("content-type", "text/html; charset=utf-8");
|
|
2074
|
+
res.end("<html><body>Login complete. You may close this window.</body></html>");
|
|
2075
|
+
resolve({ code, state });
|
|
2076
|
+
});
|
|
2077
|
+
});
|
|
2078
|
+
return { url: redirectBase, waitForCode, close: () => server.close() };
|
|
2079
|
+
}
|
|
2080
|
+
async function loginPkce(opener) {
|
|
2081
|
+
let verifier = base64url(randomBytes(48));
|
|
2082
|
+
let data = new TextEncoder().encode(verifier);
|
|
2083
|
+
let digest = await crypto.subtle.digest("SHA-256", data);
|
|
2084
|
+
let code_challenge = base64url(digest);
|
|
2085
|
+
let state = base64url(randomBytes(16));
|
|
2086
|
+
let loop = await startLoopback();
|
|
2087
|
+
let redirect_uri = `${loop.url}/callback`;
|
|
2088
|
+
let authorizeUrl = new URL(`${config.host}/authenticate`);
|
|
2089
|
+
authorizeUrl.search = new URLSearchParams({
|
|
2090
|
+
redirect_uri,
|
|
2091
|
+
code_challenge,
|
|
2092
|
+
state,
|
|
2093
|
+
client_id: AUTH_CLIENT_ID,
|
|
2094
|
+
response_type: "code",
|
|
2095
|
+
code_challenge_method: "S256",
|
|
2096
|
+
scope: AUTH_SCOPES
|
|
2097
|
+
}).toString();
|
|
2098
|
+
if (opener) await opener(authorizeUrl.toString());
|
|
2099
|
+
else openInBrowser(authorizeUrl.toString());
|
|
2100
|
+
let result = await loop.waitForCode;
|
|
2101
|
+
if (!result.code) throw new Error("No authorization code received");
|
|
2102
|
+
if (result.state !== state) throw new Error("State mismatch");
|
|
2103
|
+
let res = await fetch(`${config.host}/_api/oauth2/token`, {
|
|
2104
|
+
method: "POST",
|
|
2105
|
+
headers: { "content-type": "application/json" },
|
|
2106
|
+
body: JSON.stringify({
|
|
2107
|
+
grant_type: "authorization_code",
|
|
2108
|
+
code: result.code,
|
|
2109
|
+
redirect_uri,
|
|
2110
|
+
client_id: AUTH_CLIENT_ID,
|
|
2111
|
+
code_verifier: verifier
|
|
2112
|
+
})
|
|
2113
|
+
});
|
|
2114
|
+
if (!res.ok) throw new Error(`token exchange failed: ${res.status}`);
|
|
2115
|
+
let tokenResp = await res.json();
|
|
2116
|
+
await updateEntry(tokenResp);
|
|
2117
|
+
}
|
|
2118
|
+
async function refreshAccessToken() {
|
|
2119
|
+
let refresh_token = (await readEntry())?.refresh_token;
|
|
2120
|
+
let res = await fetch(new URL("/_api/oauth2/token", config.host).toString(), {
|
|
2121
|
+
method: "POST",
|
|
2122
|
+
headers: { "content-type": "application/json" },
|
|
2123
|
+
body: JSON.stringify({ grant_type: "refresh_token", refresh_token, client_id: AUTH_CLIENT_ID })
|
|
2124
|
+
});
|
|
2125
|
+
if (!res.ok) throw new Error(`refresh failed: ${res.status}`);
|
|
2126
|
+
let json = await res.json();
|
|
2127
|
+
await updateEntry(json);
|
|
2128
|
+
}
|
|
2129
|
+
async function authenticatedFetch(pathOrUrl, init = {}) {
|
|
2130
|
+
let entry = await readEntry();
|
|
2131
|
+
if (!entry) throw new Error("Not logged in; run `graphene login`");
|
|
2132
|
+
if (!entry.access_token || entry.expires_at < Date.now()) {
|
|
2133
|
+
await refreshAccessToken();
|
|
2134
|
+
entry = await readEntry();
|
|
2135
|
+
}
|
|
2136
|
+
let token = entry?.access_token;
|
|
2137
|
+
if (!token) throw new Error("Failed to obtain access token");
|
|
2138
|
+
let url = new URL(pathOrUrl, config.host);
|
|
2139
|
+
let headers = new Headers(init.headers || {});
|
|
2140
|
+
headers.set("authorization", `Bearer ${token}`);
|
|
2141
|
+
let res = await fetch(url.toString(), { ...init, headers });
|
|
2142
|
+
if (res.status === 401 || res.status === 403) {
|
|
2143
|
+
await refreshAccessToken();
|
|
2144
|
+
token = (await readEntry())?.access_token;
|
|
2145
|
+
if (token) {
|
|
2146
|
+
headers.set("cookie", `access_token=${token}`);
|
|
2147
|
+
res = await fetch(url.toString(), { ...init, headers });
|
|
2148
|
+
}
|
|
2149
|
+
}
|
|
2150
|
+
return res;
|
|
2151
|
+
}
|
|
2152
|
+
var AUTH_CLIENT_ID, AUTH_SCOPES, cfgDir, credsPath;
|
|
2153
|
+
var init_auth = __esm({
|
|
2154
|
+
"auth.ts"() {
|
|
2155
|
+
"use strict";
|
|
2156
|
+
init_config();
|
|
2157
|
+
AUTH_CLIENT_ID = "connected-app-test-4685a2a0-1cb9-4a81-a6bf-01a3efe7b981";
|
|
2158
|
+
AUTH_SCOPES = "offline_access";
|
|
2159
|
+
cfgDir = process.env.XDG_CONFIG_HOME || path5.join(os2.homedir(), ".config");
|
|
2160
|
+
credsPath = path5.join(cfgDir, "graphene", "credentials.json");
|
|
2161
|
+
}
|
|
2162
|
+
});
|
|
2163
|
+
|
|
1284
2164
|
// connections/bigQuery.ts
|
|
1285
2165
|
var bigQuery_exports = {};
|
|
1286
2166
|
__export(bigQuery_exports, {
|
|
@@ -1290,20 +2170,19 @@ import { BigQuery, BigQueryDate, BigQueryTimestamp } from "@google-cloud/bigquer
|
|
|
1290
2170
|
var BigQueryConnection;
|
|
1291
2171
|
var init_bigQuery = __esm({
|
|
1292
2172
|
"connections/bigQuery.ts"() {
|
|
2173
|
+
"use strict";
|
|
1293
2174
|
init_config();
|
|
1294
2175
|
BigQueryConnection = class {
|
|
1295
2176
|
client;
|
|
1296
2177
|
constructor(options = {}) {
|
|
2178
|
+
options.projectId ||= config.bigquery?.projectId;
|
|
1297
2179
|
if (process.env.GOOGLE_CREDENTIALS_CONTENT) {
|
|
1298
2180
|
let parsed = JSON.parse(process.env.GOOGLE_CREDENTIALS_CONTENT);
|
|
1299
2181
|
options.projectId = parsed.project_id;
|
|
1300
2182
|
options.credentials = parsed;
|
|
1301
2183
|
}
|
|
1302
|
-
options.projectId
|
|
1303
|
-
|
|
1304
|
-
options.userAgent ||= "Graphene";
|
|
1305
|
-
if (!options.projectId) throw new Error("googleProjectId must be set in config or provided in service account credentials");
|
|
1306
|
-
this.client = new BigQuery(options);
|
|
2184
|
+
if (!options.projectId) throw new Error("projectId must be set in config or provided in service account credentials");
|
|
2185
|
+
this.client = new BigQuery({ ...options, userAgent: "Graphene" });
|
|
1307
2186
|
}
|
|
1308
2187
|
async runQuery(sql) {
|
|
1309
2188
|
let [job] = await this.client.createQueryJob({ query: sql, useLegacySql: false });
|
|
@@ -1327,27 +2206,33 @@ var duckdb_exports = {};
|
|
|
1327
2206
|
__export(duckdb_exports, {
|
|
1328
2207
|
DuckDBConnection: () => DuckDBConnection
|
|
1329
2208
|
});
|
|
1330
|
-
import { promises as
|
|
1331
|
-
import
|
|
2209
|
+
import { promises as fs5 } from "fs";
|
|
2210
|
+
import path6 from "path";
|
|
1332
2211
|
import { DuckDBTimestampValue, DuckDBInstance, DuckDBDateValue } from "@duckdb/node-api";
|
|
1333
2212
|
var DuckDBConnection;
|
|
1334
2213
|
var init_duckdb = __esm({
|
|
1335
2214
|
"connections/duckdb.ts"() {
|
|
2215
|
+
"use strict";
|
|
1336
2216
|
init_config();
|
|
1337
2217
|
DuckDBConnection = class {
|
|
2218
|
+
options;
|
|
1338
2219
|
ready;
|
|
1339
2220
|
connection = null;
|
|
1340
|
-
constructor() {
|
|
2221
|
+
constructor(options) {
|
|
2222
|
+
this.options = options || {};
|
|
1341
2223
|
this.ready = this.initialize();
|
|
1342
2224
|
}
|
|
1343
2225
|
async initialize() {
|
|
1344
|
-
let
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
2226
|
+
let dbPath = this.options.path;
|
|
2227
|
+
if (!dbPath) {
|
|
2228
|
+
let files = await fs5.readdir(config.root);
|
|
2229
|
+
dbPath = files.find((f) => f.endsWith(".duckdb"));
|
|
2230
|
+
if (!dbPath) throw new Error("No .duckdb file found in current directory");
|
|
2231
|
+
dbPath = path6.resolve(config.root, dbPath);
|
|
2232
|
+
}
|
|
1348
2233
|
let db = await DuckDBInstance.create(":memory:");
|
|
1349
2234
|
this.connection = await db.connect();
|
|
1350
|
-
let escapedPath =
|
|
2235
|
+
let escapedPath = dbPath.replace(/'/g, "''");
|
|
1351
2236
|
await this.connection.run(`attach '${escapedPath}' as graphene_cli (READ_ONLY);`);
|
|
1352
2237
|
await this.connection.run("use graphene_cli;");
|
|
1353
2238
|
}
|
|
@@ -1372,27 +2257,114 @@ var init_duckdb = __esm({
|
|
|
1372
2257
|
}
|
|
1373
2258
|
});
|
|
1374
2259
|
|
|
2260
|
+
// connections/snowflake.ts
|
|
2261
|
+
var snowflake_exports = {};
|
|
2262
|
+
__export(snowflake_exports, {
|
|
2263
|
+
SnowflakeConnection: () => SnowflakeConnection
|
|
2264
|
+
});
|
|
2265
|
+
import { createPrivateKey } from "node:crypto";
|
|
2266
|
+
import snowflake from "snowflake-sdk";
|
|
2267
|
+
var SnowflakeConnection;
|
|
2268
|
+
var init_snowflake2 = __esm({
|
|
2269
|
+
"connections/snowflake.ts"() {
|
|
2270
|
+
"use strict";
|
|
2271
|
+
init_config();
|
|
2272
|
+
SnowflakeConnection = class {
|
|
2273
|
+
ready;
|
|
2274
|
+
connection;
|
|
2275
|
+
constructor(opts) {
|
|
2276
|
+
this.ready = this.initialize(opts || {});
|
|
2277
|
+
}
|
|
2278
|
+
async initialize(opts) {
|
|
2279
|
+
let privateKeyPath = process.env.SNOWFLAKE_PRI_KEY_PATH || config.snowflake?.privateKeyPath;
|
|
2280
|
+
let privateKeyPass = process.env.SNOWFLAKE_PRI_PASSPHRASE;
|
|
2281
|
+
let authOptions = {};
|
|
2282
|
+
if (privateKeyPath) {
|
|
2283
|
+
authOptions = { privateKeyPath, privateKeyPass };
|
|
2284
|
+
} else if (opts.privateKey) {
|
|
2285
|
+
let privateKey = createPrivateKey({ key: opts.privateKey, format: "pem", passphrase: privateKeyPass });
|
|
2286
|
+
authOptions = { privateKey: privateKey.export({ format: "pem", type: "pkcs8" }) };
|
|
2287
|
+
}
|
|
2288
|
+
snowflake.configure({ logLevel: process.env.SNOWFLAKE_LOG_LEVEL || "WARN", logFilePath: "/dev/null" });
|
|
2289
|
+
this.connection = snowflake.createConnection({
|
|
2290
|
+
...opts,
|
|
2291
|
+
...config.snowflake || {},
|
|
2292
|
+
...authOptions,
|
|
2293
|
+
authenticator: "SNOWFLAKE_JWT",
|
|
2294
|
+
application: "Graphene"
|
|
2295
|
+
});
|
|
2296
|
+
await new Promise((resolve, reject) => {
|
|
2297
|
+
this.connection.connect((err, conn) => err ? reject(err) : resolve(conn));
|
|
2298
|
+
});
|
|
2299
|
+
}
|
|
2300
|
+
async runQuery(sql) {
|
|
2301
|
+
await this.ready;
|
|
2302
|
+
return await new Promise((resolve, reject) => {
|
|
2303
|
+
let rows = [];
|
|
2304
|
+
this.connection.execute({
|
|
2305
|
+
sqlText: sql,
|
|
2306
|
+
streamResult: true,
|
|
2307
|
+
complete: (error, statement) => {
|
|
2308
|
+
if (error) {
|
|
2309
|
+
reject(new Error(`Snowflake query failed: ${error.message || error}`));
|
|
2310
|
+
return;
|
|
2311
|
+
}
|
|
2312
|
+
let stream = statement.streamRows();
|
|
2313
|
+
stream.on("error", (err) => reject(err));
|
|
2314
|
+
stream.on("readable", function(row) {
|
|
2315
|
+
while ((row = this.read()) !== null) {
|
|
2316
|
+
rows.push(row);
|
|
2317
|
+
}
|
|
2318
|
+
});
|
|
2319
|
+
stream.on("end", () => {
|
|
2320
|
+
let totalRows = Number(statement.getNumRows());
|
|
2321
|
+
resolve({ rows, totalRows });
|
|
2322
|
+
});
|
|
2323
|
+
}
|
|
2324
|
+
});
|
|
2325
|
+
});
|
|
2326
|
+
}
|
|
2327
|
+
};
|
|
2328
|
+
}
|
|
2329
|
+
});
|
|
2330
|
+
|
|
1375
2331
|
// connections/index.ts
|
|
1376
|
-
async function
|
|
2332
|
+
async function runQuery(sql) {
|
|
2333
|
+
if (config.host) {
|
|
2334
|
+
let resp = await authenticatedFetch("/_api/query", {
|
|
2335
|
+
method: "POST",
|
|
2336
|
+
headers: { "Content-Type": "application/json" },
|
|
2337
|
+
body: JSON.stringify({ sql })
|
|
2338
|
+
});
|
|
2339
|
+
return await resp.json();
|
|
2340
|
+
}
|
|
1377
2341
|
if (config.dialect === "bigquery") {
|
|
1378
2342
|
let mod = await Promise.resolve().then(() => (init_bigQuery(), bigQuery_exports));
|
|
1379
|
-
|
|
2343
|
+
let conn = new mod.BigQueryConnection();
|
|
2344
|
+
return await conn.runQuery(sql);
|
|
1380
2345
|
} else if (config.dialect === "duckdb") {
|
|
1381
2346
|
let mod = await Promise.resolve().then(() => (init_duckdb(), duckdb_exports));
|
|
1382
|
-
|
|
2347
|
+
let conn = new mod.DuckDBConnection({});
|
|
2348
|
+
return await conn.runQuery(sql);
|
|
2349
|
+
} else if (config.dialect === "snowflake") {
|
|
2350
|
+
let mod = await Promise.resolve().then(() => (init_snowflake2(), snowflake_exports));
|
|
2351
|
+
let conn = new mod.SnowflakeConnection({});
|
|
2352
|
+
return await conn.runQuery(sql);
|
|
1383
2353
|
} else {
|
|
1384
2354
|
throw new Error(`Unsupported dialect: ${config.dialect}`);
|
|
1385
2355
|
}
|
|
1386
2356
|
}
|
|
1387
2357
|
var init_connections = __esm({
|
|
1388
2358
|
"connections/index.ts"() {
|
|
2359
|
+
"use strict";
|
|
1389
2360
|
init_config();
|
|
2361
|
+
init_auth();
|
|
1390
2362
|
}
|
|
1391
2363
|
});
|
|
1392
2364
|
|
|
1393
2365
|
// mdCompile.ts
|
|
1394
|
-
import
|
|
1395
|
-
import
|
|
2366
|
+
import fs6 from "fs";
|
|
2367
|
+
import path7 from "path";
|
|
1396
2368
|
import { visit } from "unist-util-visit";
|
|
1397
2369
|
import sanitizeHtml from "sanitize-html";
|
|
1398
2370
|
function extractQueries() {
|
|
@@ -1469,13 +2441,14 @@ ${content}`;
|
|
|
1469
2441
|
}
|
|
1470
2442
|
function componentNames() {
|
|
1471
2443
|
if (cachedComponentNames) return cachedComponentNames;
|
|
1472
|
-
let files =
|
|
1473
|
-
cachedComponentNames = files.map((f) =>
|
|
2444
|
+
let files = fs6.readdirSync(path7.join(import.meta.dirname, "../ui/components"));
|
|
2445
|
+
cachedComponentNames = files.map((f) => path7.basename(f, ".svelte")).filter((f) => !f.startsWith("_"));
|
|
1474
2446
|
return cachedComponentNames || [];
|
|
1475
2447
|
}
|
|
1476
2448
|
var cachedComponentNames;
|
|
1477
2449
|
var init_mdCompile = __esm({
|
|
1478
2450
|
"mdCompile.ts"() {
|
|
2451
|
+
"use strict";
|
|
1479
2452
|
cachedComponentNames = null;
|
|
1480
2453
|
}
|
|
1481
2454
|
});
|
|
@@ -1483,23 +2456,19 @@ var init_mdCompile = __esm({
|
|
|
1483
2456
|
// serve2.ts
|
|
1484
2457
|
var serve2_exports = {};
|
|
1485
2458
|
__export(serve2_exports, {
|
|
1486
|
-
mockFileMap: () => mockFileMap,
|
|
1487
2459
|
serve2: () => serve2
|
|
1488
2460
|
});
|
|
1489
2461
|
import { createServer, optimizeDeps } from "vite";
|
|
1490
2462
|
import { svelte, vitePreprocess } from "@sveltejs/vite-plugin-svelte";
|
|
1491
|
-
import
|
|
1492
|
-
import
|
|
2463
|
+
import fs7 from "fs-extra";
|
|
2464
|
+
import crypto2 from "crypto";
|
|
1493
2465
|
import { mdsvex } from "mdsvex";
|
|
1494
|
-
import
|
|
2466
|
+
import path8 from "path";
|
|
1495
2467
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1496
|
-
import { WebSocketServer } from "ws";
|
|
1497
|
-
import { spawn as spawn2 } from "child_process";
|
|
1498
2468
|
async function serve2() {
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
await
|
|
1502
|
-
await fs5.writeFile(path6.resolve(grapheneRoot, `node_modules/.graphene/${process.env.NODE_ENV == "test" ? "test" : "serve"}.pid`), String(process.pid));
|
|
2469
|
+
uiRoot = path8.join(fileURLToPath2(import.meta.url), "../../ui");
|
|
2470
|
+
let port = Number(process.env.GRAPHENE_PORT) || 4e3;
|
|
2471
|
+
await fs7.ensureDir(path8.resolve(config.root, "node_modules/.graphene"));
|
|
1503
2472
|
let server = await createServer({
|
|
1504
2473
|
root: config.root,
|
|
1505
2474
|
plugins: [
|
|
@@ -1515,24 +2484,30 @@ async function serve2() {
|
|
|
1515
2484
|
injectComponentImports()
|
|
1516
2485
|
]
|
|
1517
2486
|
}),
|
|
2487
|
+
checkVitePlugin(),
|
|
1518
2488
|
handleRequestPlugin,
|
|
1519
2489
|
updateWorkspacePlugin,
|
|
1520
2490
|
mockFilesForTests()
|
|
1521
2491
|
],
|
|
2492
|
+
publicDir: path8.resolve(uiRoot),
|
|
1522
2493
|
server: {
|
|
1523
|
-
port
|
|
2494
|
+
port,
|
|
1524
2495
|
fs: { strict: false },
|
|
1525
2496
|
strictPort: true
|
|
1526
2497
|
},
|
|
1527
2498
|
resolve: {
|
|
1528
2499
|
alias: {
|
|
1529
|
-
graphene:
|
|
2500
|
+
graphene: path8.resolve(uiRoot, "web.js")
|
|
1530
2501
|
}
|
|
1531
2502
|
}
|
|
2503
|
+
// optimizeDeps: { // this seems prudent in tests, but currently breaks because ssf needs to be optimized, even in tests
|
|
2504
|
+
// noDiscovery: process.env.NODE_ENV == 'test',
|
|
2505
|
+
// include: process.env.NODE_ENV == 'test' ? [] : undefined,
|
|
2506
|
+
// },
|
|
1532
2507
|
});
|
|
1533
2508
|
await optimizeDeps(server.config);
|
|
1534
2509
|
await server.listen();
|
|
1535
|
-
console.log(`Server running at http://localhost:${
|
|
2510
|
+
console.log(`Server running at http://localhost:${port}`);
|
|
1536
2511
|
return server;
|
|
1537
2512
|
}
|
|
1538
2513
|
async function handleQuery(req, res) {
|
|
@@ -1549,44 +2524,18 @@ async function handleQuery(req, res) {
|
|
|
1549
2524
|
}
|
|
1550
2525
|
if (queries.length > 1) throw new Error("Found multiple queries, which could be a parsing error");
|
|
1551
2526
|
let sql = toSql(queries[0], params);
|
|
1552
|
-
let hash =
|
|
2527
|
+
let hash = crypto2.createHash("SHA1").update(sql).digest("hex");
|
|
1553
2528
|
res.setHeader("ETag", hash);
|
|
1554
2529
|
if (hashes.includes(hash) && req.headers["cache-control"] != "no-cache") {
|
|
1555
2530
|
res.statusCode = 304;
|
|
1556
2531
|
return res.end();
|
|
1557
2532
|
}
|
|
1558
|
-
let
|
|
1559
|
-
let queryResults = await connection.runQuery(sql);
|
|
2533
|
+
let queryResults = await runQuery(sql);
|
|
1560
2534
|
let totalRows = queryResults.totalRows ?? queryResults.rows.length;
|
|
1561
2535
|
if (totalRows > queryResults.rows.length) throw new Error("Query returns too many rows");
|
|
1562
2536
|
let fields = queries[0].fields.map((f) => ({ name: f.name, type: f.type }));
|
|
1563
2537
|
res.end(JSON.stringify({ rows: queryResults.rows, hash, fields, sql }));
|
|
1564
2538
|
}
|
|
1565
|
-
async function handleView(req, res) {
|
|
1566
|
-
let chunks = [];
|
|
1567
|
-
for await (let chunk of req) chunks.push(chunk);
|
|
1568
|
-
let { mdFile, chart } = JSON.parse(Buffer.concat(chunks).toString());
|
|
1569
|
-
let id = Math.random().toString(36).slice(2);
|
|
1570
|
-
res.setHeader("Content-Type", "application/json");
|
|
1571
|
-
viewRequests[id] = { response: res };
|
|
1572
|
-
let pageUrl = "/" + mdFile.replace(/\.md$/, "").replace(/^\//, "");
|
|
1573
|
-
if (pageUrl === "/index") pageUrl = "/";
|
|
1574
|
-
pageUrl = `http://localhost:${config.port || 4e3}${pageUrl}`;
|
|
1575
|
-
let conn = browserConnections.find((conn2) => conn2.url === pageUrl);
|
|
1576
|
-
if (!conn) {
|
|
1577
|
-
spawn2("open", [pageUrl]);
|
|
1578
|
-
let end = Date.now() + 5e3;
|
|
1579
|
-
while (Date.now() < end && !conn) {
|
|
1580
|
-
conn = browserConnections.find((conn2) => conn2.url === pageUrl);
|
|
1581
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1582
|
-
}
|
|
1583
|
-
if (!conn) {
|
|
1584
|
-
res.statusCode = 500;
|
|
1585
|
-
return res.end(JSON.stringify({ error: "No browser tab available and failed to open one" }));
|
|
1586
|
-
}
|
|
1587
|
-
}
|
|
1588
|
-
conn.socket.send(JSON.stringify({ type: "view", chart, requestId: id }));
|
|
1589
|
-
}
|
|
1590
2539
|
async function handlePage(server, res, filePath, mount) {
|
|
1591
2540
|
res.setHeader("Content-Type", "text/html");
|
|
1592
2541
|
let mdMount = mount ? `
|
|
@@ -1599,7 +2548,7 @@ async function handlePage(server, res, filePath, mount) {
|
|
|
1599
2548
|
<meta charset="UTF-8" />
|
|
1600
2549
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
1601
2550
|
<title>Graphene</title>
|
|
1602
|
-
<link rel="icon" href="
|
|
2551
|
+
<link rel="icon" href="/assets/favicon.ico" />
|
|
1603
2552
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
1604
2553
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
1605
2554
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap" rel="stylesheet">
|
|
@@ -1609,10 +2558,7 @@ async function handlePage(server, res, filePath, mount) {
|
|
|
1609
2558
|
<div id="content"></div>
|
|
1610
2559
|
</main>
|
|
1611
2560
|
<script type="module">
|
|
1612
|
-
// do this first so we can track errors caused by importing the md file
|
|
1613
|
-
import 'graphene'
|
|
1614
|
-
</script>
|
|
1615
|
-
<script type="module">
|
|
2561
|
+
import 'graphene' // do this first so we can track errors caused by importing the md file
|
|
1616
2562
|
${mdMount}
|
|
1617
2563
|
</script>
|
|
1618
2564
|
</body>
|
|
@@ -1625,63 +2571,45 @@ function mockFilesForTests() {
|
|
|
1625
2571
|
name: "mock-files-for-tests",
|
|
1626
2572
|
enforce: "pre",
|
|
1627
2573
|
resolveId(id) {
|
|
1628
|
-
if (mockFileMap[id.replace(
|
|
2574
|
+
if (mockFileMap[id.replace(config.root + "/", "")]) return id + "?mock";
|
|
1629
2575
|
},
|
|
1630
2576
|
load(id) {
|
|
1631
2577
|
if (!id.endsWith("?mock")) return null;
|
|
1632
|
-
return mockFileMap[id.replace(
|
|
2578
|
+
return mockFileMap[id.replace(config.root + "/", "").replace(/\?mock$/, "")];
|
|
1633
2579
|
}
|
|
1634
2580
|
};
|
|
1635
2581
|
}
|
|
1636
|
-
var
|
|
2582
|
+
var uiRoot, workspaceLoadPromise, updateWorkspacePlugin, handleRequestPlugin;
|
|
1637
2583
|
var init_serve2 = __esm({
|
|
1638
2584
|
"serve2.ts"() {
|
|
2585
|
+
"use strict";
|
|
1639
2586
|
init_core();
|
|
1640
2587
|
init_connections();
|
|
1641
2588
|
init_mdCompile();
|
|
2589
|
+
init_check();
|
|
2590
|
+
init_mockFiles();
|
|
1642
2591
|
updateWorkspacePlugin = {
|
|
1643
2592
|
name: "updateWorkspace",
|
|
1644
2593
|
configureServer: (s) => {
|
|
1645
2594
|
s.watcher.add("**/*.gsql");
|
|
1646
2595
|
s.watcher.on("change", () => {
|
|
1647
2596
|
clearWorkspace();
|
|
1648
|
-
workspaceLoadPromise = loadWorkspace(
|
|
2597
|
+
workspaceLoadPromise = loadWorkspace(config.root, false);
|
|
1649
2598
|
});
|
|
1650
|
-
workspaceLoadPromise = loadWorkspace(
|
|
2599
|
+
workspaceLoadPromise = loadWorkspace(config.root, false);
|
|
1651
2600
|
}
|
|
1652
2601
|
};
|
|
1653
2602
|
handleRequestPlugin = {
|
|
1654
2603
|
name: "handleRequest",
|
|
1655
2604
|
configureServer: (s) => {
|
|
1656
|
-
let wss = new WebSocketServer({ noServer: true });
|
|
1657
|
-
s.httpServer.on("upgrade", (req, socket, head) => {
|
|
1658
|
-
if (!req.url?.endsWith("/graphene-ws")) return;
|
|
1659
|
-
wss.handleUpgrade(req, socket, head, (ws) => {
|
|
1660
|
-
wss.emit("connection", ws, req);
|
|
1661
|
-
});
|
|
1662
|
-
});
|
|
1663
|
-
wss.on("connection", (socket) => {
|
|
1664
|
-
socket.on("message", (data) => {
|
|
1665
|
-
let message = JSON.parse(data.toString());
|
|
1666
|
-
if (message.type === "register") {
|
|
1667
|
-
browserConnections.push({ url: message.url, socket });
|
|
1668
|
-
}
|
|
1669
|
-
if (message.type === "viewResponse") {
|
|
1670
|
-
viewRequests[message.requestId].response.end(JSON.stringify(message));
|
|
1671
|
-
delete viewRequests[message.requestId];
|
|
1672
|
-
}
|
|
1673
|
-
});
|
|
1674
|
-
socket.on("close", () => browserConnections = browserConnections.filter((conn) => conn.socket !== socket));
|
|
1675
|
-
});
|
|
1676
2605
|
s.middlewares.use(async function handleRequest(req, res, next) {
|
|
1677
2606
|
try {
|
|
1678
2607
|
let [pathName] = (req.url || "").split("?");
|
|
1679
2608
|
if (pathName == "/_api/query") return await handleQuery(req, res);
|
|
1680
|
-
if (pathName == "/graphene/view") return await handleView(req, res);
|
|
1681
2609
|
if (pathName == "/__ct") return await handlePage(s, res, "__ct", false);
|
|
1682
2610
|
if (!pathName || pathName == "/") pathName = "index";
|
|
1683
|
-
let mdPath =
|
|
1684
|
-
if (await
|
|
2611
|
+
let mdPath = path8.join(config.root, pathName + ".md");
|
|
2612
|
+
if (await fs7.exists(mdPath)) {
|
|
1685
2613
|
await handlePage(s, res, mdPath, true);
|
|
1686
2614
|
} else {
|
|
1687
2615
|
next();
|
|
@@ -1694,166 +2622,20 @@ var init_serve2 = __esm({
|
|
|
1694
2622
|
});
|
|
1695
2623
|
}
|
|
1696
2624
|
};
|
|
1697
|
-
browserConnections = [];
|
|
1698
|
-
viewRequests = {};
|
|
1699
|
-
mockFileMap = {};
|
|
1700
2625
|
}
|
|
1701
2626
|
});
|
|
1702
2627
|
|
|
1703
2628
|
// cli.ts
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
// printer.ts
|
|
1707
|
-
init_core();
|
|
1708
|
-
import { styleText as nodeStyleText } from "node:util";
|
|
1709
|
-
import Table from "cli-table3";
|
|
1710
|
-
import chalk from "chalk";
|
|
1711
|
-
var styleText = (style, text) => {
|
|
1712
|
-
try {
|
|
1713
|
-
return nodeStyleText ? nodeStyleText(style, text) : text;
|
|
1714
|
-
} catch {
|
|
1715
|
-
return text;
|
|
1716
|
-
}
|
|
1717
|
-
};
|
|
1718
|
-
function offsetToLineCol(src, offset2) {
|
|
1719
|
-
let lines = src.split(/\r?\n/);
|
|
1720
|
-
let acc = 0;
|
|
1721
|
-
for (let i = 0; i < lines.length; i++) {
|
|
1722
|
-
let lineText = lines[i];
|
|
1723
|
-
let nextAcc = acc + lineText.length + 1;
|
|
1724
|
-
if (offset2 < nextAcc || i === lines.length - 1) {
|
|
1725
|
-
let col = Math.max(0, offset2 - acc);
|
|
1726
|
-
return { line: i + 1, col, lineStart: acc, lineText };
|
|
1727
|
-
}
|
|
1728
|
-
acc = nextAcc;
|
|
1729
|
-
}
|
|
1730
|
-
return { line: 1, col: 0, lineStart: 0, lineText: lines[0] || "" };
|
|
1731
|
-
}
|
|
1732
|
-
function printDiagnostics(diags) {
|
|
1733
|
-
let parts = [];
|
|
1734
|
-
for (let d of diags) {
|
|
1735
|
-
let src = getFile2(d.file)?.contents || "";
|
|
1736
|
-
let { line, col, lineStart, lineText } = offsetToLineCol(src, d.from.offset);
|
|
1737
|
-
let endCol = Math.max(col + 1, Math.min(lineText.length, d.to.offset - lineStart));
|
|
1738
|
-
let caretLen = Math.max(1, endCol - col);
|
|
1739
|
-
let sev = d.severity === "error" ? "red" : "yellow";
|
|
1740
|
-
let header = `${styleText(sev, d.severity.toUpperCase())}: ${d.file} line ${line}: ${d.message}`;
|
|
1741
|
-
let gutter = " | ";
|
|
1742
|
-
let caretLine = `${" ".repeat(col)}${styleText(sev, "^".repeat(caretLen))}`;
|
|
1743
|
-
parts.push([header, `${gutter}${lineText}`, `${gutter}${caretLine}`].join("\n"));
|
|
1744
|
-
}
|
|
1745
|
-
if (parts.length) console.error(parts.join("\n"));
|
|
1746
|
-
}
|
|
1747
|
-
function printTable(rows) {
|
|
1748
|
-
if (!rows || rows.length === 0) {
|
|
1749
|
-
console.log(chalk.yellow("No results returned"));
|
|
1750
|
-
return;
|
|
1751
|
-
}
|
|
1752
|
-
let headers = Object.keys(rows[0]);
|
|
1753
|
-
let table2 = new Table({ head: headers.map((h) => chalk.blue(h)) });
|
|
1754
|
-
let MAX_DISPLAY_ROWS = 200;
|
|
1755
|
-
let displayRows = rows.slice(0, MAX_DISPLAY_ROWS);
|
|
1756
|
-
displayRows.forEach((row) => table2.push(headers.map((h) => row[h]?.toString() || "")));
|
|
1757
|
-
console.log(table2.toString());
|
|
1758
|
-
if (rows.length > MAX_DISPLAY_ROWS) {
|
|
1759
|
-
console.log(chalk.yellow(`Displayed first ${MAX_DISPLAY_ROWS} rows (of ${rows.length} total).`));
|
|
1760
|
-
}
|
|
1761
|
-
}
|
|
1762
|
-
|
|
1763
|
-
// cli.ts
|
|
2629
|
+
init_printer();
|
|
1764
2630
|
init_core();
|
|
1765
2631
|
init_config();
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
import os from "os";
|
|
1769
|
-
|
|
1770
|
-
// background.ts
|
|
1771
|
-
import { spawn } from "child_process";
|
|
1772
|
-
import { fileURLToPath } from "url";
|
|
1773
|
-
import fs2 from "fs-extra";
|
|
1774
|
-
import path3 from "path";
|
|
1775
|
-
async function runServeInBackground() {
|
|
1776
|
-
let root = process.cwd();
|
|
1777
|
-
let grapheneCache = getGrapheneCache(root);
|
|
1778
|
-
let logFile = path3.join(grapheneCache, "serve.log");
|
|
1779
|
-
await fs2.ensureDir(grapheneCache);
|
|
1780
|
-
await stopGrapheneIfRunning(root);
|
|
1781
|
-
let log = fs2.openSync(logFile, "w");
|
|
1782
|
-
let entryPoint = process.argv[1] || fileURLToPath(import.meta.url);
|
|
1783
|
-
let childArgs = [...process.execArgv, entryPoint, "serve", "--fg", ...process.argv.slice(3)];
|
|
1784
|
-
let child = spawn(process.execPath, childArgs, {
|
|
1785
|
-
cwd: root,
|
|
1786
|
-
detached: true,
|
|
1787
|
-
env: { ...process.env },
|
|
1788
|
-
stdio: ["ignore", log, log]
|
|
1789
|
-
});
|
|
1790
|
-
if (!child.pid) throw new Error("Failed to start server process");
|
|
1791
|
-
await new Promise((resolve, reject) => {
|
|
1792
|
-
let buffer = "";
|
|
1793
|
-
fs2.watchFile(logFile, { interval: 200 }, (curr, prev) => {
|
|
1794
|
-
if (curr.size > prev.size) {
|
|
1795
|
-
let stream = fs2.createReadStream(logFile, { start: 0, end: curr.size - 1 });
|
|
1796
|
-
stream.on("data", (d) => {
|
|
1797
|
-
process.stdout.write(d);
|
|
1798
|
-
buffer = (buffer + d.toString()).slice(-200);
|
|
1799
|
-
if (buffer.includes("Server running at http://localhost:")) resolve();
|
|
1800
|
-
});
|
|
1801
|
-
}
|
|
1802
|
-
});
|
|
1803
|
-
child.once("exit", () => {
|
|
1804
|
-
process.stdout.write(fs2.readFileSync(logFile));
|
|
1805
|
-
reject(new Error("Exited before server started"));
|
|
1806
|
-
});
|
|
1807
|
-
child.once("error", (e) => reject(e));
|
|
1808
|
-
});
|
|
1809
|
-
}
|
|
1810
|
-
function getGrapheneCache(root) {
|
|
1811
|
-
return path3.join(root, "node_modules", ".graphene");
|
|
1812
|
-
}
|
|
1813
|
-
function getPidFilePath(root) {
|
|
1814
|
-
return path3.join(getGrapheneCache(root), process.env.NODE_ENV == "test" ? "test.pid" : "serve.pid");
|
|
1815
|
-
}
|
|
1816
|
-
async function stopGrapheneIfRunning(root) {
|
|
1817
|
-
let pidFile = getPidFilePath(root);
|
|
1818
|
-
let pid = await readPid(pidFile);
|
|
1819
|
-
if (!pid) return true;
|
|
1820
|
-
if (!isProcessRunning(pid)) {
|
|
1821
|
-
await fs2.remove(pidFile);
|
|
1822
|
-
return true;
|
|
1823
|
-
}
|
|
1824
|
-
try {
|
|
1825
|
-
console.log(`Stopping server (${pid})`);
|
|
1826
|
-
process.kill(pid, "SIGTERM");
|
|
1827
|
-
} catch (err) {
|
|
1828
|
-
if (err.code === "ESRCH") return true;
|
|
1829
|
-
return false;
|
|
1830
|
-
}
|
|
1831
|
-
let end = Date.now() + 5e3;
|
|
1832
|
-
while (Date.now() < end && isProcessRunning(pid)) {
|
|
1833
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1834
|
-
}
|
|
1835
|
-
await fs2.remove(pidFile);
|
|
1836
|
-
return !isProcessRunning(pid);
|
|
1837
|
-
}
|
|
1838
|
-
async function readPid(pidFile) {
|
|
1839
|
-
if (!await fs2.pathExists(pidFile)) return void 0;
|
|
1840
|
-
let contents = (await fs2.readFile(pidFile, "utf8")).trim();
|
|
1841
|
-
if (!contents) return void 0;
|
|
1842
|
-
let pid = Number.parseInt(contents, 10);
|
|
1843
|
-
if (Number.isNaN(pid)) return void 0;
|
|
1844
|
-
return pid;
|
|
1845
|
-
}
|
|
1846
|
-
function isProcessRunning(pid) {
|
|
1847
|
-
try {
|
|
1848
|
-
process.kill(pid, 0);
|
|
1849
|
-
return true;
|
|
1850
|
-
} catch {
|
|
1851
|
-
return false;
|
|
1852
|
-
}
|
|
1853
|
-
}
|
|
1854
|
-
|
|
1855
|
-
// cli.ts
|
|
2632
|
+
init_background();
|
|
2633
|
+
init_check();
|
|
1856
2634
|
init_connections();
|
|
2635
|
+
init_auth();
|
|
2636
|
+
import { Command } from "commander";
|
|
2637
|
+
import fs8 from "fs-extra";
|
|
2638
|
+
import path9 from "path";
|
|
1857
2639
|
var program = new Command();
|
|
1858
2640
|
program.name("graphene").description("Graphene CLI").version("1.0.0");
|
|
1859
2641
|
program.hook("preAction", async () => {
|
|
@@ -1875,53 +2657,30 @@ program.command("run").description("Run a query against your database").argument
|
|
|
1875
2657
|
let queries = analyze(gsql);
|
|
1876
2658
|
if (!validQuery(queries)) return;
|
|
1877
2659
|
let sql = toSql(queries[0]);
|
|
1878
|
-
let
|
|
1879
|
-
let res = await connection.runQuery(sql);
|
|
2660
|
+
let res = await runQuery(sql);
|
|
1880
2661
|
printTable(res.rows);
|
|
1881
2662
|
});
|
|
1882
|
-
program.command("serve").description("Run the local server").option("--
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
await mod.serve2();
|
|
1886
|
-
} else {
|
|
2663
|
+
program.command("serve").description("Run the local server").option("--bg", "Run the server in the background").action(async (options) => {
|
|
2664
|
+
await stopGrapheneIfRunning();
|
|
2665
|
+
if (options.bg) {
|
|
1887
2666
|
await runServeInBackground();
|
|
1888
2667
|
process.exit(0);
|
|
2668
|
+
} else {
|
|
2669
|
+
let mod = await Promise.resolve().then(() => (init_serve2(), serve2_exports));
|
|
2670
|
+
await mod.serve2();
|
|
1889
2671
|
}
|
|
1890
2672
|
});
|
|
1891
2673
|
program.command("stop").description("Stop the local server").action(async () => {
|
|
1892
|
-
await stopGrapheneIfRunning(
|
|
2674
|
+
await stopGrapheneIfRunning();
|
|
1893
2675
|
});
|
|
1894
|
-
program.command("check").description("Check the project for errors").action(async () => {
|
|
1895
|
-
await
|
|
1896
|
-
|
|
1897
|
-
if (getDiagnostics().length) {
|
|
1898
|
-
printDiagnostics(getDiagnostics());
|
|
1899
|
-
process.exit(1);
|
|
1900
|
-
}
|
|
1901
|
-
console.log("No errors found \u{1F48E}");
|
|
2676
|
+
program.command("check").description("Check the project for errors, optionally capturing a page screenshot").argument("[mdFile]", "Markdown file to check (e.g., index.md)").option("-c, --chart <chartTitle>", "Title of a specific chart to capture").action(async (mdArg, options) => {
|
|
2677
|
+
let res = await check({ mdArg, chart: options.chart });
|
|
2678
|
+
process.exit(res ? 0 : 1);
|
|
1902
2679
|
});
|
|
1903
|
-
program.command("
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
body: JSON.stringify({ mdFile, chart: options.chart })
|
|
1908
|
-
});
|
|
1909
|
-
if (!response.ok) throw new Error(`View request failed: ${await response.text()}`);
|
|
1910
|
-
let result = await response.json();
|
|
1911
|
-
if (result.errors && result.errors.length > 0) {
|
|
1912
|
-
console.error("Errors found:");
|
|
1913
|
-
result.errors.forEach((error) => console.error(JSON.stringify(error)));
|
|
1914
|
-
}
|
|
1915
|
-
if (result.stillLoading) {
|
|
1916
|
-
console.error("Warning: Queries were still loading when the screenshot was taken");
|
|
1917
|
-
}
|
|
1918
|
-
if (result.screenshot) {
|
|
1919
|
-
let filename = `graphene-screenshot-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.png`;
|
|
1920
|
-
let screenshotPath = path7.join(os.tmpdir(), filename);
|
|
1921
|
-
let base64Data = result.screenshot.replace(/^data:image\/png;base64,/, "");
|
|
1922
|
-
await fs6.writeFile(screenshotPath, base64Data, "base64");
|
|
1923
|
-
console.log("Screenshot saved to", screenshotPath);
|
|
1924
|
-
}
|
|
2680
|
+
program.command("login").description("Log in to Graphene Cloud").action(async () => {
|
|
2681
|
+
await loginPkce();
|
|
2682
|
+
console.log("Successfully logged in");
|
|
2683
|
+
process.exit(0);
|
|
1925
2684
|
});
|
|
1926
2685
|
program.parse(process.argv);
|
|
1927
2686
|
async function readInput(arg) {
|
|
@@ -1934,9 +2693,9 @@ async function readInput(arg) {
|
|
|
1934
2693
|
process.stdin.resume();
|
|
1935
2694
|
});
|
|
1936
2695
|
}
|
|
1937
|
-
let absolutePath =
|
|
1938
|
-
if (
|
|
1939
|
-
return await
|
|
2696
|
+
let absolutePath = path9.resolve(arg);
|
|
2697
|
+
if (fs8.existsSync(absolutePath)) {
|
|
2698
|
+
return await fs8.promises.readFile(absolutePath, "utf-8");
|
|
1940
2699
|
}
|
|
1941
2700
|
return arg;
|
|
1942
2701
|
}
|