@ganju/utils 0.0.16 → 0.0.17
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/dist/constants.d.ts +3 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +12 -1
- package/dist/customCodeToken.d.ts +11 -0
- package/dist/customCodeToken.d.ts.map +1 -1
- package/dist/customCodeToken.js +41 -1
- package/dist/data.d.ts +4 -0
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +26 -1
- package/dist/dataApi.d.ts +68 -1
- package/dist/dataApi.d.ts.map +1 -1
- package/dist/dataApi.js +45 -1
- package/dist/dataCompiler.d.ts.map +1 -1
- package/dist/dataCompiler.js +5 -1
- package/dist/dataCsv.d.ts +20 -0
- package/dist/dataCsv.d.ts.map +1 -0
- package/dist/dataCsv.js +102 -0
- package/dist/dataEngine.d.ts.map +1 -1
- package/dist/dataEngine.js +24 -4
- package/dist/dataImport.d.ts +2 -0
- package/dist/dataImport.d.ts.map +1 -1
- package/dist/dataImport.js +4 -1
- package/dist/dataQueries.d.ts +73 -0
- package/dist/dataQueries.d.ts.map +1 -0
- package/dist/dataQueries.js +513 -0
- package/dist/dataRpc.d.ts +19 -0
- package/dist/dataRpc.d.ts.map +1 -1
- package/dist/dataTools.d.ts +40 -0
- package/dist/dataTools.d.ts.map +1 -0
- package/dist/dataTools.js +280 -0
- package/dist/dataTypes.d.ts +2 -0
- package/dist/dataTypes.d.ts.map +1 -1
- package/dist/index.d.ts +86 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/schema.d.ts +91 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +65 -1
- package/dist/sdkConstants.d.ts +1 -0
- package/dist/sdkConstants.d.ts.map +1 -1
- package/dist/sdkConstants.js +2 -1
- package/package.json +1 -1
- package/src/constants.ts +13 -1
- package/src/customCodeToken.ts +74 -0
- package/src/data.ts +34 -0
- package/src/dataApi.ts +53 -2
- package/src/dataCompiler.ts +5 -1
- package/src/dataCsv.ts +99 -0
- package/src/dataEngine.ts +24 -4
- package/src/dataImport.ts +3 -0
- package/src/dataQueries.ts +730 -0
- package/src/dataRpc.ts +11 -0
- package/src/dataTools.ts +338 -0
- package/src/dataTypes.ts +6 -0
- package/src/index.ts +9 -1
- package/src/schema.ts +69 -1
- package/src/sdkConstants.ts +1 -0
package/src/dataCsv.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading a CSV file into rows of text, with no parser package behind it:
|
|
3
|
+
* the CLI's import and the owner's data-admin tool read files the same way.
|
|
4
|
+
*
|
|
5
|
+
* RFC 4180 plus the two things spreadsheets actually write: a byte-order mark
|
|
6
|
+
* in front, and `;` between fields where the decimal mark is a comma (Excel in
|
|
7
|
+
* Spanish). The separator is read from the header line, so the same file
|
|
8
|
+
* exported from either locale imports the same way.
|
|
9
|
+
*
|
|
10
|
+
* Every cell comes back as text. Deciding what "28.000" means is the shared
|
|
11
|
+
* import code's job, so the CLI and the dashboard decide it the same way.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export interface ParsedCsv {
|
|
15
|
+
headers: string[];
|
|
16
|
+
rows: string[][];
|
|
17
|
+
// The file line each row starts on, counting the header as line 1, since a
|
|
18
|
+
// quoted cell can span lines and blank lines are skipped.
|
|
19
|
+
lines: number[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const SEPARATORS = [',', ';', '\t'] as const;
|
|
23
|
+
|
|
24
|
+
// The separator that appears most in the first line, outside quotes.
|
|
25
|
+
export const detectSeparator = (text: string): string => {
|
|
26
|
+
const counts = new Map<string, number>(SEPARATORS.map(s => [s, 0]));
|
|
27
|
+
let quoted = false;
|
|
28
|
+
for (const char of text) {
|
|
29
|
+
if (char === '"') quoted = !quoted;
|
|
30
|
+
else if (!quoted && (char === '\n' || char === '\r')) break;
|
|
31
|
+
else if (!quoted && counts.has(char))
|
|
32
|
+
counts.set(char, counts.get(char)! + 1);
|
|
33
|
+
}
|
|
34
|
+
let best = ',';
|
|
35
|
+
for (const [separator, count] of counts) {
|
|
36
|
+
if (count > counts.get(best)!) best = separator;
|
|
37
|
+
}
|
|
38
|
+
return best;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const parseCsv = (input: string, separator?: string): ParsedCsv => {
|
|
42
|
+
const text = input.charCodeAt(0) === 0xfeff ? input.slice(1) : input;
|
|
43
|
+
const sep = separator ?? detectSeparator(text);
|
|
44
|
+
const records: { cells: string[]; line: number }[] = [];
|
|
45
|
+
let cells: string[] = [];
|
|
46
|
+
let cell = '';
|
|
47
|
+
let quoted = false;
|
|
48
|
+
let line = 1;
|
|
49
|
+
let startLine = 1;
|
|
50
|
+
|
|
51
|
+
const endCell = () => {
|
|
52
|
+
cells.push(cell);
|
|
53
|
+
cell = '';
|
|
54
|
+
};
|
|
55
|
+
const endRecord = () => {
|
|
56
|
+
endCell();
|
|
57
|
+
// A blank line, or one of nothing but separators, is not a row.
|
|
58
|
+
if (cells.some(c => c.trim() !== '')) {
|
|
59
|
+
records.push({ cells, line: startLine });
|
|
60
|
+
}
|
|
61
|
+
cells = [];
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
for (let i = 0; i < text.length; i++) {
|
|
65
|
+
const char = text[i];
|
|
66
|
+
if (quoted) {
|
|
67
|
+
if (char === '"') {
|
|
68
|
+
if (text[i + 1] === '"') {
|
|
69
|
+
cell += '"';
|
|
70
|
+
i++;
|
|
71
|
+
} else quoted = false;
|
|
72
|
+
} else {
|
|
73
|
+
if (char === '\n') line++;
|
|
74
|
+
cell += char;
|
|
75
|
+
}
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (char === '"' && cell === '') {
|
|
79
|
+
quoted = true;
|
|
80
|
+
} else if (char === sep) {
|
|
81
|
+
endCell();
|
|
82
|
+
} else if (char === '\n' || char === '\r') {
|
|
83
|
+
if (char === '\r' && text[i + 1] === '\n') i++;
|
|
84
|
+
endRecord();
|
|
85
|
+
line++;
|
|
86
|
+
startLine = line;
|
|
87
|
+
} else {
|
|
88
|
+
cell += char;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (cell !== '' || cells.length) endRecord();
|
|
92
|
+
|
|
93
|
+
const [header, ...body] = records;
|
|
94
|
+
return {
|
|
95
|
+
headers: (header?.cells ?? []).map(h => h.trim()),
|
|
96
|
+
rows: body.map(r => r.cells),
|
|
97
|
+
lines: body.map(r => r.line)
|
|
98
|
+
};
|
|
99
|
+
};
|
package/src/dataEngine.ts
CHANGED
|
@@ -172,6 +172,22 @@ export const INTERNAL_MIGRATIONS: readonly (readonly string[])[] = [
|
|
|
172
172
|
WHERE c.table_id = _ganju_tables.id AND c.type = 'text'
|
|
173
173
|
ORDER BY c.position, c.id LIMIT 1
|
|
174
174
|
) WHERE display_column_id IS NULL AND created_at < '2026-10-24T00:00:00Z'`
|
|
175
|
+
],
|
|
176
|
+
// Named queries: the owner's SELECTs, run by name. `tables` and `plan` are
|
|
177
|
+
// what the check found when each was saved.
|
|
178
|
+
[
|
|
179
|
+
`CREATE TABLE IF NOT EXISTS _ganju_queries (
|
|
180
|
+
name TEXT PRIMARY KEY,
|
|
181
|
+
description TEXT,
|
|
182
|
+
sql TEXT NOT NULL,
|
|
183
|
+
params TEXT NOT NULL DEFAULT '[]',
|
|
184
|
+
assistant INTEGER NOT NULL DEFAULT 0,
|
|
185
|
+
tables TEXT NOT NULL DEFAULT '[]',
|
|
186
|
+
plan TEXT NOT NULL DEFAULT '[]',
|
|
187
|
+
created_at TEXT NOT NULL DEFAULT (${DATA_NOW_SQL}),
|
|
188
|
+
updated_at TEXT NOT NULL DEFAULT (${DATA_NOW_SQL}),
|
|
189
|
+
updated_by TEXT
|
|
190
|
+
) WITHOUT ROWID`
|
|
175
191
|
]
|
|
176
192
|
];
|
|
177
193
|
|
|
@@ -271,7 +287,8 @@ export const loadSchema = (runner: SqlRunner): DataSchema => {
|
|
|
271
287
|
position: Number(c.position)
|
|
272
288
|
};
|
|
273
289
|
})
|
|
274
|
-
}))
|
|
290
|
+
})),
|
|
291
|
+
timeZone: getMeta(runner, 'time_zone')
|
|
275
292
|
};
|
|
276
293
|
};
|
|
277
294
|
|
|
@@ -1356,7 +1373,8 @@ const insertValues = (
|
|
|
1356
1373
|
// A bot may only point a relation at a row it could read: none in a table
|
|
1357
1374
|
// hidden from it, and only its own customer's in a scoped one. Otherwise it
|
|
1358
1375
|
// could link an order to another customer and read their name back as the
|
|
1359
|
-
// label.
|
|
1376
|
+
// label. A caller held to some tables (a function's `data.tables`) may not
|
|
1377
|
+
// point outside them either.
|
|
1360
1378
|
const assertRelationsVisible = (
|
|
1361
1379
|
runner: SqlRunner,
|
|
1362
1380
|
schema: DataSchema,
|
|
@@ -1364,7 +1382,7 @@ const assertRelationsVisible = (
|
|
|
1364
1382
|
table: DataTable,
|
|
1365
1383
|
values: Map<string, SqlValue>
|
|
1366
1384
|
) => {
|
|
1367
|
-
if (!caller.restricted) return;
|
|
1385
|
+
if (!caller.restricted && !caller.tables) return;
|
|
1368
1386
|
for (const [name, value] of values) {
|
|
1369
1387
|
if (value === null) continue;
|
|
1370
1388
|
const column = table.columns.find(c => c.name === name);
|
|
@@ -1373,7 +1391,9 @@ const assertRelationsVisible = (
|
|
|
1373
1391
|
if (!target) continue;
|
|
1374
1392
|
if (!canRead(caller, target)) {
|
|
1375
1393
|
throw new DataError(
|
|
1376
|
-
|
|
1394
|
+
caller.restricted
|
|
1395
|
+
? `${table.name}.${name} points at a table the assistant can't use.`
|
|
1396
|
+
: `${table.name}.${name} points at ${target.name}, which isn't one of this caller's tables.`,
|
|
1377
1397
|
'forbidden'
|
|
1378
1398
|
);
|
|
1379
1399
|
}
|
package/src/dataImport.ts
CHANGED