@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.
Files changed (57) hide show
  1. package/dist/constants.d.ts +3 -0
  2. package/dist/constants.d.ts.map +1 -1
  3. package/dist/constants.js +12 -1
  4. package/dist/customCodeToken.d.ts +11 -0
  5. package/dist/customCodeToken.d.ts.map +1 -1
  6. package/dist/customCodeToken.js +41 -1
  7. package/dist/data.d.ts +4 -0
  8. package/dist/data.d.ts.map +1 -1
  9. package/dist/data.js +26 -1
  10. package/dist/dataApi.d.ts +68 -1
  11. package/dist/dataApi.d.ts.map +1 -1
  12. package/dist/dataApi.js +45 -1
  13. package/dist/dataCompiler.d.ts.map +1 -1
  14. package/dist/dataCompiler.js +5 -1
  15. package/dist/dataCsv.d.ts +20 -0
  16. package/dist/dataCsv.d.ts.map +1 -0
  17. package/dist/dataCsv.js +102 -0
  18. package/dist/dataEngine.d.ts.map +1 -1
  19. package/dist/dataEngine.js +24 -4
  20. package/dist/dataImport.d.ts +2 -0
  21. package/dist/dataImport.d.ts.map +1 -1
  22. package/dist/dataImport.js +4 -1
  23. package/dist/dataQueries.d.ts +73 -0
  24. package/dist/dataQueries.d.ts.map +1 -0
  25. package/dist/dataQueries.js +513 -0
  26. package/dist/dataRpc.d.ts +19 -0
  27. package/dist/dataRpc.d.ts.map +1 -1
  28. package/dist/dataTools.d.ts +40 -0
  29. package/dist/dataTools.d.ts.map +1 -0
  30. package/dist/dataTools.js +280 -0
  31. package/dist/dataTypes.d.ts +2 -0
  32. package/dist/dataTypes.d.ts.map +1 -1
  33. package/dist/index.d.ts +86 -2
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +2 -0
  36. package/dist/schema.d.ts +91 -0
  37. package/dist/schema.d.ts.map +1 -1
  38. package/dist/schema.js +65 -1
  39. package/dist/sdkConstants.d.ts +1 -0
  40. package/dist/sdkConstants.d.ts.map +1 -1
  41. package/dist/sdkConstants.js +2 -1
  42. package/package.json +1 -1
  43. package/src/constants.ts +13 -1
  44. package/src/customCodeToken.ts +74 -0
  45. package/src/data.ts +34 -0
  46. package/src/dataApi.ts +53 -2
  47. package/src/dataCompiler.ts +5 -1
  48. package/src/dataCsv.ts +99 -0
  49. package/src/dataEngine.ts +24 -4
  50. package/src/dataImport.ts +3 -0
  51. package/src/dataQueries.ts +730 -0
  52. package/src/dataRpc.ts +11 -0
  53. package/src/dataTools.ts +338 -0
  54. package/src/dataTypes.ts +6 -0
  55. package/src/index.ts +9 -1
  56. package/src/schema.ts +69 -1
  57. 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
- `${table.name}.${name} points at a table the assistant can't use.`,
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
@@ -552,3 +552,6 @@ export const csvLine = (cells: ImportCell[]) =>
552
552
  return /[",\n\r]/.test(value) ? `"${value.replace(/"/g, '""')}"` : value;
553
553
  })
554
554
  .join(',');
555
+
556
+ export { detectSeparator, parseCsv } from './dataCsv';
557
+ export type { ParsedCsv } from './dataCsv';