json-fs-query 3.3.1 → 3.5.0

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 (145) hide show
  1. package/README.md +104 -2
  2. package/browser/jsonFsQuery.js +810 -3845
  3. package/browser/jsonFsQuery.min.js +1 -1
  4. package/dist/cli/chooseDatabase.d.ts +13 -0
  5. package/dist/cli/chooseDatabase.js +37 -0
  6. package/dist/cli/chooseDatabase.min.js +1 -0
  7. package/dist/cli/formatCsv.d.ts +9 -0
  8. package/dist/cli/formatCsv.js +53 -0
  9. package/dist/cli/formatCsv.min.js +1 -0
  10. package/dist/cli/formatResult.d.ts +30 -0
  11. package/dist/cli/formatResult.js +139 -0
  12. package/dist/cli/formatResult.min.js +1 -0
  13. package/dist/cli/index.d.ts +7 -0
  14. package/dist/cli/index.js +52 -0
  15. package/dist/cli/index.min.js +2 -0
  16. package/dist/cli/interactive.d.ts +15 -0
  17. package/dist/cli/interactive.js +257 -0
  18. package/dist/cli/interactive.min.js +1 -0
  19. package/dist/cli/output.d.ts +31 -0
  20. package/dist/cli/output.js +64 -0
  21. package/dist/cli/output.min.js +1 -0
  22. package/dist/cli/runDirect.d.ts +11 -0
  23. package/dist/cli/runDirect.js +28 -0
  24. package/dist/cli/runDirect.min.js +1 -0
  25. package/dist/data/introspect/alter.js +2 -10
  26. package/dist/data/introspect/alter.min.js +1 -1
  27. package/dist/data/introspect/create.js +15 -4
  28. package/dist/data/introspect/create.min.js +1 -1
  29. package/dist/data/introspect/definitions/key.js +0 -2
  30. package/dist/data/introspect/definitions/key.min.js +1 -1
  31. package/dist/data/introspect/definitions/record.js +0 -2
  32. package/dist/data/introspect/definitions/record.min.js +1 -1
  33. package/dist/data/introspect/describe.js +2 -0
  34. package/dist/data/introspect/describe.min.js +1 -1
  35. package/dist/data/introspect/drop.js +3 -4
  36. package/dist/data/introspect/drop.min.js +1 -1
  37. package/dist/data/introspect/formatDatabase.js +0 -2
  38. package/dist/data/introspect/formatDatabase.min.js +1 -1
  39. package/dist/data/introspect/index.js +0 -4
  40. package/dist/data/introspect/index.min.js +1 -1
  41. package/dist/data/introspect/packDatabase.js +0 -19
  42. package/dist/data/introspect/packDatabase.min.js +1 -1
  43. package/dist/data/queries/createRecord.js +0 -2
  44. package/dist/data/queries/createRecord.min.js +1 -1
  45. package/dist/data/queries/databaseStatements.js +0 -3
  46. package/dist/data/queries/databaseStatements.min.js +1 -1
  47. package/dist/data/queries/deleteEntity.js +0 -20
  48. package/dist/data/queries/deleteEntity.min.js +1 -1
  49. package/dist/data/queries/dropRecord.js +0 -5
  50. package/dist/data/queries/dropRecord.min.js +1 -1
  51. package/dist/data/queries/groupBy.js +0 -16
  52. package/dist/data/queries/groupBy.min.js +1 -1
  53. package/dist/data/queries/insertEntity.js +0 -35
  54. package/dist/data/queries/insertEntity.min.js +1 -1
  55. package/dist/data/queries/joinEntity.js +0 -15
  56. package/dist/data/queries/joinEntity.min.js +1 -1
  57. package/dist/data/queries/parser.js +0 -2
  58. package/dist/data/queries/parser.min.js +1 -1
  59. package/dist/data/queries/selectFields.js +0 -3
  60. package/dist/data/queries/selectFields.min.js +1 -1
  61. package/dist/data/queries/showRecords.js +0 -2
  62. package/dist/data/queries/showRecords.min.js +1 -1
  63. package/dist/data/queries/updateEntity.js +0 -4
  64. package/dist/data/queries/updateEntity.min.js +1 -1
  65. package/dist/data/queries/where.js +0 -4
  66. package/dist/data/queries/where.min.js +1 -1
  67. package/dist/data/storage/databases.js +1 -2
  68. package/dist/data/storage/databases.min.js +1 -1
  69. package/dist/data/storage/listFiles.js +0 -2
  70. package/dist/data/storage/listFiles.min.js +1 -1
  71. package/dist/data/storage/readCache.js +0 -13
  72. package/dist/data/storage/readCache.min.js +1 -1
  73. package/dist/data/storage/readJson.js +2 -2
  74. package/dist/data/storage/readJson.min.js +1 -1
  75. package/dist/data/storage/storagePath.js +0 -1
  76. package/dist/data/storage/storagePath.min.js +1 -1
  77. package/dist/data/storage/writeSet.js +0 -32
  78. package/dist/data/storage/writeSet.min.js +1 -1
  79. package/dist/data/utilities/assertValidName.d.ts +14 -0
  80. package/dist/data/utilities/assertValidName.js +34 -0
  81. package/dist/data/utilities/assertValidName.min.js +1 -0
  82. package/dist/data/utilities/computeKeyValue.js +0 -2
  83. package/dist/data/utilities/computeKeyValue.min.js +1 -1
  84. package/dist/data/utilities/conditionEntities.js +0 -20
  85. package/dist/data/utilities/conditionEntities.min.js +1 -1
  86. package/dist/data/utilities/conditionTree.js +0 -3
  87. package/dist/data/utilities/conditionTree.min.js +1 -1
  88. package/dist/data/utilities/config.d.ts +11 -0
  89. package/dist/data/utilities/config.js +15 -1
  90. package/dist/data/utilities/config.min.js +1 -1
  91. package/dist/data/utilities/databaseLock.js +0 -14
  92. package/dist/data/utilities/databaseLock.min.js +1 -1
  93. package/dist/data/utilities/deregisterRecord.js +0 -2
  94. package/dist/data/utilities/deregisterRecord.min.js +1 -1
  95. package/dist/data/utilities/fieldType.js +0 -2
  96. package/dist/data/utilities/fieldType.min.js +1 -1
  97. package/dist/data/utilities/getConditionIndexedList.js +0 -20
  98. package/dist/data/utilities/getConditionIndexedList.min.js +1 -1
  99. package/dist/data/utilities/indexBuffer.js +0 -33
  100. package/dist/data/utilities/indexBuffer.min.js +1 -1
  101. package/dist/data/utilities/indexHas.js +0 -3
  102. package/dist/data/utilities/indexHas.min.js +1 -1
  103. package/dist/data/utilities/joinBases.js +0 -33
  104. package/dist/data/utilities/joinBases.min.js +1 -1
  105. package/dist/data/utilities/keyIndexFiles.js +0 -19
  106. package/dist/data/utilities/keyIndexFiles.min.js +1 -1
  107. package/dist/data/utilities/knownFields.js +0 -21
  108. package/dist/data/utilities/knownFields.min.js +1 -1
  109. package/dist/data/utilities/mergeJoin.js +0 -2
  110. package/dist/data/utilities/mergeJoin.min.js +1 -1
  111. package/dist/data/utilities/packedTable.js +0 -3
  112. package/dist/data/utilities/packedTable.min.js +1 -1
  113. package/dist/data/utilities/parsers/findConditions.js +0 -2
  114. package/dist/data/utilities/parsers/findConditions.min.js +1 -1
  115. package/dist/data/utilities/parsers/findGroupBy.js +0 -2
  116. package/dist/data/utilities/parsers/findGroupBy.min.js +1 -1
  117. package/dist/data/utilities/parsers/maskStrings.js +0 -3
  118. package/dist/data/utilities/parsers/maskStrings.min.js +1 -1
  119. package/dist/data/utilities/parsers/parseCreate.js +0 -21
  120. package/dist/data/utilities/parsers/parseCreate.min.js +1 -1
  121. package/dist/data/utilities/parsers/splitClauses.js +0 -16
  122. package/dist/data/utilities/parsers/splitClauses.min.js +1 -1
  123. package/dist/data/utilities/parsers/splitQueries.js +0 -3
  124. package/dist/data/utilities/parsers/splitQueries.min.js +1 -1
  125. package/dist/data/utilities/reconcileJoin.js +0 -4
  126. package/dist/data/utilities/reconcileJoin.min.js +1 -1
  127. package/dist/data/utilities/removeFromIndex.js +0 -3
  128. package/dist/data/utilities/removeFromIndex.min.js +1 -1
  129. package/dist/data/utilities/requireNode.js +0 -1
  130. package/dist/data/utilities/requireNode.min.js +1 -1
  131. package/dist/data/utilities/retrieveRecord.js +0 -1
  132. package/dist/data/utilities/retrieveRecord.min.js +1 -1
  133. package/dist/data/utilities/retrieveRecords.js +0 -16
  134. package/dist/data/utilities/retrieveRecords.min.js +1 -1
  135. package/dist/data/utilities/rowLookup.js +0 -15
  136. package/dist/data/utilities/rowLookup.min.js +1 -1
  137. package/dist/data/utilities/runQuery.js +0 -16
  138. package/dist/data/utilities/runQuery.min.js +1 -1
  139. package/dist/data/utilities/semiJoin.js +0 -19
  140. package/dist/data/utilities/semiJoin.min.js +1 -1
  141. package/dist/data/utilities/updateIndex.js +0 -2
  142. package/dist/data/utilities/updateIndex.min.js +1 -1
  143. package/dist/index.js +0 -4
  144. package/dist/index.min.js +1 -1
  145. package/package.json +9 -3
@@ -0,0 +1,13 @@
1
+ export type DatabaseOptions = {
2
+ database?: string;
3
+ databases?: string;
4
+ };
5
+ /**
6
+ * Point json-fs-query at the database the options name: `-d <path>` alone is one database folder (databasePath),
7
+ * `--databases <folder>` a folder of databases (databasesPath) with `-d <name>` picking one in it.
8
+ * @param options
9
+ * @returns {Promise<string>} A line saying which database is in use.
10
+ * @throws {Error} For an invalid database name, or a database that doesn't exist.
11
+ */
12
+ export declare const chooseDatabase: (options: DatabaseOptions) => Promise<string>;
13
+ //# sourceMappingURL=chooseDatabase.d.ts.map
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.chooseDatabase = void 0;
7
+ var _databaseStatements = require("../data/queries/databaseStatements");
8
+ var _config = require("../data/utilities/config");
9
+ /**
10
+ * Point json-fs-query at the database the options name: `-d <path>` alone is one database folder (databasePath),
11
+ * `--databases <folder>` a folder of databases (databasesPath) with `-d <name>` picking one in it.
12
+ * @param options
13
+ * @returns {Promise<string>} A line saying which database is in use.
14
+ * @throws {Error} For an invalid database name, or a database that doesn't exist.
15
+ */
16
+ const chooseDatabase = async options => {
17
+ if (options.databases) {
18
+ (0, _config.configure)({
19
+ databasesPath: options.databases,
20
+ ...(options.database ? {
21
+ database: options.database
22
+ } : {})
23
+ });
24
+ if (!options.database) {
25
+ return `Databases: ${options.databases} (none in use yet: use <name>;)`;
26
+ }
27
+ // Checks that it exists (configure only checks the name)
28
+ await (0, _databaseStatements.useDatabase)(options.database);
29
+ } else if (options.database) {
30
+ // databasePath is joined straight onto file names, so it needs its trailing slash (`-d data` is data/)
31
+ (0, _config.configure)({
32
+ databasePath: options.database.endsWith('/') ? options.database : `${options.database}/`
33
+ });
34
+ }
35
+ return `Database: ${(0, _config.activeDatabasePath)()}`;
36
+ };
37
+ exports.chooseDatabase = chooseDatabase;
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.chooseDatabase=void 0;var _databaseStatements=require("../data/queries/databaseStatements"),_config=require("../data/utilities/config");const chooseDatabase=async a=>{if(a.databases){if((0,_config.configure)({databasesPath:a.databases,...a.database?{database:a.database}:{}}),!a.database)return`Databases: ${a.databases} (none in use yet: use <name>;)`;await(0,_databaseStatements.useDatabase)(a.database)}else a.database&&(0,_config.configure)({databasePath:a.database.endsWith("/")?a.database:`${a.database}/`});return`Database: ${(0,_config.activeDatabasePath)()}`};exports.chooseDatabase=chooseDatabase;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Format one query's result as CSV: a header line of field names (in the order they first appear), then a line per
3
+ * row. A grouped read is its rows, plain values (show records) one `value` column, an empty result nothing at all.
4
+ * Anything else (`describe`, a single value) isn't a table: an object prints as JSON, a value as it is.
5
+ * @param result One entry of what query() resolves to
6
+ * @returns {string}
7
+ */
8
+ export declare const formatCsv: (result: unknown) => string;
9
+ //# sourceMappingURL=formatCsv.d.ts.map
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.formatCsv = void 0;
7
+ require("core-js/modules/es.json.stringify.js");
8
+ var _formatResult = require("./formatResult");
9
+ const isPlainObject = value => value !== null && typeof value === 'object' && !Array.isArray(value);
10
+ /**
11
+ * One CSV field (RFC 4180): quoted when it holds a comma, a quote, a line break, or space at either end, with its
12
+ * quotes doubled. `null` and a missing field are empty and unquoted, the empty string is `""` (as psql's CSV output
13
+ * tells them apart). Objects and arrays are their JSON.
14
+ * @param value
15
+ * @param present False when the row doesn't have the field
16
+ */
17
+ const csvField = (value, present = true) => {
18
+ if (!present || value === null || typeof value === 'undefined') {
19
+ return '';
20
+ }
21
+ const text = typeof value === 'object' ? JSON.stringify(value) : String(value);
22
+ return text === '' || /[",\r\n]|^\s|\s$/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
23
+ };
24
+ /**
25
+ * The rows of a grouped read, in group order (each row still holds the fields it was grouped by).
26
+ * @param groups
27
+ */
28
+ const groupRows = groups => groups.flatMap(group => group.groups ? groupRows(group.groups) : group.rows);
29
+ /**
30
+ * Format one query's result as CSV: a header line of field names (in the order they first appear), then a line per
31
+ * row. A grouped read is its rows, plain values (show records) one `value` column, an empty result nothing at all.
32
+ * Anything else (`describe`, a single value) isn't a table: an object prints as JSON, a value as it is.
33
+ * @param result One entry of what query() resolves to
34
+ * @returns {string}
35
+ */
36
+ const formatCsv = result => {
37
+ if (!Array.isArray(result)) {
38
+ return isPlainObject(result) ? JSON.stringify(result, null, 2) : csvField(result);
39
+ }
40
+ const rows = result.length && result.every(_formatResult.isGroup) ? groupRows(result) : result;
41
+ if (!rows.length) {
42
+ return '';
43
+ }
44
+ // A boolean, not a type guard: a guard would narrow rows to never in the branch below
45
+ const objects = rows.every(isPlainObject);
46
+ if (!objects) {
47
+ return ['value', ...rows.map(row => csvField(row))].join('\n');
48
+ }
49
+ const columns = [...new Set(rows.flatMap(row => Object.keys(row)))];
50
+ const has = (row, column) => Object.prototype.hasOwnProperty.call(row, column);
51
+ return [columns.map(column => csvField(column)).join(','), ...rows.map(row => columns.map(column => csvField(row[column], has(row, column))).join(','))].join('\n');
52
+ };
53
+ exports.formatCsv = formatCsv;
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.formatCsv=void 0,require("core-js/modules/es.json.stringify.js");var _formatResult=require("./formatResult");const isPlainObject=r=>null!==r&&"object"==typeof r&&!Array.isArray(r),csvField=(r,e=!0)=>{if(!e||null==r)return"";const t="object"==typeof r?JSON.stringify(r):String(r);return""===t||/[",\r\n]|^\s|\s$/.test(t)?`"${t.replace(/"/g,'""')}"`:t},groupRows=r=>r.flatMap((r=>r.groups?groupRows(r.groups):r.rows)),formatCsv=r=>{if(!Array.isArray(r))return isPlainObject(r)?JSON.stringify(r,null,2):csvField(r);const e=r.length&&r.every(_formatResult.isGroup)?groupRows(r):r;if(!e.length)return"";if(!e.every(isPlainObject))return["value",...e.map((r=>csvField(r)))].join("\n");const t=[...new Set(e.flatMap((r=>Object.keys(r))))];return[t.map((r=>csvField(r))).join(","),...e.map((r=>t.map((e=>csvField(r[e],((r,e)=>Object.prototype.hasOwnProperty.call(r,e))(r,e)))).join(",")))].join("\n")};exports.formatCsv=formatCsv;
@@ -0,0 +1,30 @@
1
+ import type { Group } from '../data/queries/groupBy';
2
+ /**
3
+ * How a cell shows a value that isn't plain text. Each marker reads without colour (piped output, NO_COLOR, screen
4
+ * readers); dimming is only extra.
5
+ */
6
+ export type Markers = {
7
+ missing: string;
8
+ null: string;
9
+ empty: string;
10
+ };
11
+ export type FormatOptions = {
12
+ color?: boolean;
13
+ markers?: Partial<Markers>;
14
+ legend?: boolean;
15
+ };
16
+ export declare const defaultMarkers: Markers;
17
+ /**
18
+ * Whether a value is one group of a grouped read: a field name, then either its rows or its groups, and nothing else.
19
+ * @param value
20
+ */
21
+ export declare const isGroup: (value: unknown) => value is Group;
22
+ /**
23
+ * Format one query's result for the terminal. Rows become a table, a grouped read a table per group, an empty list
24
+ * `(0 rows)`; anything else (`describe`, a database name) prints as it is, or as JSON for an object.
25
+ * @param result One entry of what query() resolves to
26
+ * @param options
27
+ * @returns {string}
28
+ */
29
+ export declare const formatResult: (result: unknown, options?: FormatOptions) => string;
30
+ //# sourceMappingURL=formatResult.d.ts.map
@@ -0,0 +1,139 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.isGroup = exports.formatResult = exports.defaultMarkers = void 0;
7
+ require("core-js/modules/es.json.stringify.js");
8
+ var _util = require("util");
9
+ const defaultMarkers = exports.defaultMarkers = {
10
+ missing: '—',
11
+ null: 'NULL',
12
+ empty: '\'\''
13
+ };
14
+ const dim = (text, color) => color ? `\u001b[2m${text}\u001b[22m` : text;
15
+ // Width as it shows in the terminal: colour codes take no space
16
+ const visibleWidth = text => (0, _util.stripVTControlCharacters)(text).length;
17
+ const isPlainObject = value => value !== null && typeof value === 'object' && !Array.isArray(value);
18
+ const groupKeys = new Set(['field', 'value', 'missing', 'rows', 'groups']);
19
+ /**
20
+ * Whether a value is one group of a grouped read: a field name, then either its rows or its groups, and nothing else.
21
+ * @param value
22
+ */
23
+ const isGroup = value => isPlainObject(value) && typeof value['field'] === 'string' && Array.isArray(value['rows']) !== Array.isArray(value['groups']) && Object.keys(value).every(key => groupKeys.has(key));
24
+ /**
25
+ * The cell for one value. Text is shown as it is (line breaks as \n so a row stays on one line), numbers and booleans
26
+ * as written, objects and arrays as compact JSON.
27
+ * @param value
28
+ * @param present False when the row doesn't have the field at all
29
+ */
30
+ exports.isGroup = isGroup;
31
+ const toCell = (value, present = true) => {
32
+ if (!present) {
33
+ return {
34
+ text: '',
35
+ marker: 'missing'
36
+ };
37
+ }
38
+ if (value === null) {
39
+ return {
40
+ text: '',
41
+ marker: 'null'
42
+ };
43
+ }
44
+ if (value === '') {
45
+ return {
46
+ text: '',
47
+ marker: 'empty'
48
+ };
49
+ }
50
+ if (typeof value === 'string') {
51
+ return {
52
+ text: value.replace(/\r?\n/g, '\\n')
53
+ };
54
+ }
55
+ if (typeof value === 'object') {
56
+ return {
57
+ text: JSON.stringify(value)
58
+ };
59
+ }
60
+ return {
61
+ text: String(value)
62
+ };
63
+ };
64
+ const showCell = (cell, settings) => cell.marker ? dim(settings.markers[cell.marker], settings.color) : cell.text;
65
+ const rowCount = count => `(${count} ${count === 1 ? 'row' : 'rows'})`;
66
+ /**
67
+ * Draw rows as a table: columns in the order the fields first appear, a count underneath, and a legend when a marker
68
+ * that needs one is shown.
69
+ * @param rows Plain objects, or plain values (shown as one `value` column)
70
+ * @param settings
71
+ */
72
+ const formatTable = (rows, settings) => {
73
+ if (!rows.length) {
74
+ return rowCount(0);
75
+ }
76
+ const objects = rows.every(isPlainObject);
77
+ const columns = objects ? [...new Set(rows.flatMap(row => Object.keys(row)))] : ['value'];
78
+ const has = (row, column) => Object.prototype.hasOwnProperty.call(row, column);
79
+ const cells = rows.map(row => objects ? columns.map(column => toCell(row[column], has(row, column))) : [toCell(row)]);
80
+ const body = cells.map(rowCells => rowCells.map(cell => showCell(cell, settings)));
81
+ const widths = columns.map((column, i) => Math.max(visibleWidth(column), ...body.map(texts => visibleWidth(texts[i]))));
82
+ const pad = (text, i) => text + ' '.repeat(widths[i] - visibleWidth(text));
83
+ const line = '+' + widths.map(width => '-'.repeat(width + 2)).join('+') + '+';
84
+ const tableRow = texts => '| ' + texts.map(pad).join(' | ') + ' |';
85
+ const lines = [line, tableRow(columns), line, ...body.map(tableRow), line, rowCount(rows.length)];
86
+ if (settings.legend) {
87
+ // Only the markers this table shows (an empty string explains itself)
88
+ const shown = new Set(cells.flat().map(cell => cell.marker));
89
+ const legend = ['missing', 'null'].filter(marker => shown.has(marker)).map(marker => `${dim(settings.markers[marker], settings.color)} ${marker}`);
90
+ if (legend.length) {
91
+ lines.push(legend.join(' · '));
92
+ }
93
+ }
94
+ return lines.join('\n');
95
+ };
96
+ const indent = (text, depth) => text.split('\n').map(line => ' '.repeat(depth) + line).join('\n');
97
+ /**
98
+ * Draw the groups of a grouped read: a heading per group (`date = 2024-06-05 (3 rows)`), then its table, or its own
99
+ * groups indented under it.
100
+ * @param groups
101
+ * @param settings
102
+ * @param depth How far nested groups are indented
103
+ */
104
+ const formatGroups = (groups, settings, depth = 0) => groups.map(group => {
105
+ const heading = group.missing ? `${group.field} is missing` : `${group.field} = ${showCell(toCell(group.value), settings)}`;
106
+ const content = group.groups ? `${heading} (${group.groups.length} ${group.groups.length === 1 ? 'group' : 'groups'})\n${formatGroups(group.groups, settings, depth + 1)}` : `${heading} ${rowCount(group.rows.length)}\n${indent(formatTable(group.rows, settings), 1)}`;
107
+ return indent(content, depth);
108
+ }).join('\n\n');
109
+ /**
110
+ * Format one query's result for the terminal. Rows become a table, a grouped read a table per group, an empty list
111
+ * `(0 rows)`; anything else (`describe`, a database name) prints as it is, or as JSON for an object.
112
+ * @param result One entry of what query() resolves to
113
+ * @param options
114
+ * @returns {string}
115
+ */
116
+ const formatResult = (result, options = {}) => {
117
+ const settings = {
118
+ color: options.color ?? false,
119
+ markers: {
120
+ ...defaultMarkers,
121
+ ...options.markers
122
+ },
123
+ legend: options.legend ?? true
124
+ };
125
+ if (Array.isArray(result)) {
126
+ if (result.length && result.every(isGroup)) {
127
+ return formatGroups(result, settings);
128
+ }
129
+ if (result.every(isPlainObject) || result.every(value => !isPlainObject(value) && !Array.isArray(value))) {
130
+ return formatTable(result, settings);
131
+ }
132
+ return JSON.stringify(result, null, 2);
133
+ }
134
+ if (isPlainObject(result)) {
135
+ return JSON.stringify(result, null, 2);
136
+ }
137
+ return showCell(toCell(result), settings);
138
+ };
139
+ exports.formatResult = formatResult;
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.isGroup=exports.formatResult=exports.defaultMarkers=void 0,require("core-js/modules/es.json.stringify.js");var _util=require("util");const defaultMarkers=exports.defaultMarkers={missing:"—",null:"NULL",empty:"''"},dim=(e,r)=>r?`${e}`:e,visibleWidth=e=>(0,_util.stripVTControlCharacters)(e).length,isPlainObject=e=>null!==e&&"object"==typeof e&&!Array.isArray(e),groupKeys=new Set(["field","value","missing","rows","groups"]),isGroup=e=>isPlainObject(e)&&"string"==typeof e.field&&Array.isArray(e.rows)!==Array.isArray(e.groups)&&Object.keys(e).every((e=>groupKeys.has(e)));exports.isGroup=isGroup;const toCell=(e,r=!0)=>r?null===e?{text:"",marker:"null"}:""===e?{text:"",marker:"empty"}:"string"==typeof e?{text:e.replace(/\r?\n/g,"\\n")}:"object"==typeof e?{text:JSON.stringify(e)}:{text:String(e)}:{text:"",marker:"missing"},showCell=(e,r)=>e.marker?dim(r.markers[e.marker],r.color):e.text,rowCount=e=>`(${e} ${1===e?"row":"rows"})`,formatTable=(e,r)=>{if(!e.length)return rowCount(0);const t=e.every(isPlainObject),s=t?[...new Set(e.flatMap((e=>Object.keys(e))))]:["value"],o=e.map((e=>t?s.map((r=>toCell(e[r],((e,r)=>Object.prototype.hasOwnProperty.call(e,r))(e,r)))):[toCell(e)])),l=o.map((e=>e.map((e=>showCell(e,r))))),i=s.map(((e,r)=>Math.max(visibleWidth(e),...l.map((e=>visibleWidth(e[r])))))),a=(e,r)=>e+" ".repeat(i[r]-visibleWidth(e)),n="+"+i.map((e=>"-".repeat(e+2))).join("+")+"+",p=e=>"| "+e.map(a).join(" | ")+" |",u=[n,p(s),n,...l.map(p),n,rowCount(e.length)];if(r.legend){const e=new Set(o.flat().map((e=>e.marker))),t=["missing","null"].filter((r=>e.has(r))).map((e=>`${dim(r.markers[e],r.color)} ${e}`));t.length&&u.push(t.join(" · "))}return u.join("\n")},indent=(e,r)=>e.split("\n").map((e=>" ".repeat(r)+e)).join("\n"),formatGroups=(e,r,t=0)=>e.map((e=>{const s=e.missing?`${e.field} is missing`:`${e.field} = ${showCell(toCell(e.value),r)}`,o=e.groups?`${s} (${e.groups.length} ${1===e.groups.length?"group":"groups"})\n${formatGroups(e.groups,r,t+1)}`:`${s} ${rowCount(e.rows.length)}\n${indent(formatTable(e.rows,r),1)}`;return indent(o,t)})).join("\n\n"),formatResult=(e,r={})=>{const t={color:r.color??!1,markers:{...defaultMarkers,...r.markers},legend:r.legend??!0};return Array.isArray(e)?e.length&&e.every(isGroup)?formatGroups(e,t):e.every(isPlainObject)||e.every((e=>!isPlainObject(e)&&!Array.isArray(e)))?formatTable(e,t):JSON.stringify(e,null,2):isPlainObject(e)?JSON.stringify(e,null,2):showCell(toCell(e),t)};exports.formatResult=formatResult;
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Resolves once the session is idle: every line handled (the prompt), or the query finished (-q).
4
+ * @returns {Promise<void>}
5
+ */
6
+ export declare const whenIdle: () => Promise<void>;
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.whenIdle = void 0;
8
+ var _commander = require("commander");
9
+ var _interactive = require("./interactive");
10
+ var _output = require("./output");
11
+ var _runDirect = require("./runDirect");
12
+ // The package's own version (package.json sits two folders up from both src/cli and dist/cli)
13
+ const {
14
+ version
15
+ } = require('../../package.json');
16
+ // Command-line options (commander exits with a message for an unknown option, and prints --help and --version)
17
+ const program = new _commander.Command().name('jfsq').description('Query a json-fs-query database from the command line: with -q, run the query and exit; without it, open the prompt.').version(version, '-v, --version', 'print the version').option('-q, --query <query>', 'run this query (or several, separated by ;), print the results and exit').option('-d, --database <path>', 'the database folder (default database/); with --databases, the name of a database in that folder').option('--databases <folder>', 'a folder holding several databases (create database, use, show databases)').addOption(new _commander.Option('-o, --output <format>', 'how results print (at the prompt: /output <format>)').choices(_output.outputFormats).default('table')).option('--null <text>', 'how a table shows null (default NULL; at the prompt: /null <text>)').option('--missing <text>', 'how a table shows a missing field (default —; at the prompt: /missing <text>)').helpOption('-h, --help', 'print this help').addHelpText('after', `
18
+ Examples:
19
+ jfsq open the prompt on database/
20
+ jfsq -d tasks-db -q "read tasks where done = false"
21
+ jfsq --databases dbs -d real -q "show records"
22
+ jfsq -o json -q "read tasks" | jq .
23
+ jfsq -o csv -q "read tasks" > tasks.csv`).parse(process.argv);
24
+ const options = program.opts();
25
+ // Shared with the prompt, whose /output, /null and /missing commands change it
26
+ const settings = {
27
+ format: options.output,
28
+ markers: {
29
+ ...(typeof options.null === 'string' ? {
30
+ null: options.null
31
+ } : {}),
32
+ ...(typeof options.missing === 'string' ? {
33
+ missing: options.missing
34
+ } : {})
35
+ }
36
+ };
37
+ // With -q the query runs straight away and the exit code says whether it worked; otherwise the prompt opens
38
+ const session = typeof options.query === 'string' ? (() => {
39
+ const done = (0, _runDirect.runDirect)(options.query, options, settings).then(code => {
40
+ // exitCode rather than exit(): process.exit can cut off output still being written to a pipe
41
+ process.exitCode = code;
42
+ });
43
+ return {
44
+ whenIdle: () => done
45
+ };
46
+ })() : (0, _interactive.startInteractive)(options, settings);
47
+ /**
48
+ * Resolves once the session is idle: every line handled (the prompt), or the query finished (-q).
49
+ * @returns {Promise<void>}
50
+ */
51
+ const whenIdle = () => session.whenIdle();
52
+ exports.whenIdle = whenIdle;
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.whenIdle=void 0;var _commander=require("commander"),_interactive=require("./interactive"),_output=require("./output"),_runDirect=require("./runDirect");const{version:version}=require("../../package.json"),program=(new _commander.Command).name("jfsq").description("Query a json-fs-query database from the command line: with -q, run the query and exit; without it, open the prompt.").version(version,"-v, --version","print the version").option("-q, --query <query>","run this query (or several, separated by ;), print the results and exit").option("-d, --database <path>","the database folder (default database/); with --databases, the name of a database in that folder").option("--databases <folder>","a folder holding several databases (create database, use, show databases)").addOption(new _commander.Option("-o, --output <format>","how results print (at the prompt: /output <format>)").choices(_output.outputFormats).default("table")).option("--null <text>","how a table shows null (default NULL; at the prompt: /null <text>)").option("--missing <text>","how a table shows a missing field (default —; at the prompt: /missing <text>)").helpOption("-h, --help","print this help").addHelpText("after",'\nExamples:\n jfsq open the prompt on database/\n jfsq -d tasks-db -q "read tasks where done = false"\n jfsq --databases dbs -d real -q "show records"\n jfsq -o json -q "read tasks" | jq .\n jfsq -o csv -q "read tasks" > tasks.csv').parse(process.argv),options=program.opts(),settings={format:options.output,markers:{..."string"==typeof options.null?{null:options.null}:{},..."string"==typeof options.missing?{missing:options.missing}:{}}},session="string"==typeof options.query?(()=>{const e=(0,_runDirect.runDirect)(options.query,options,settings).then((e=>{process.exitCode=e}));return{whenIdle:()=>e}})():(0,_interactive.startInteractive)(options,settings),whenIdle=()=>session.whenIdle();exports.whenIdle=whenIdle;
@@ -0,0 +1,15 @@
1
+ import { DatabaseOptions } from './chooseDatabase';
2
+ import { OutputSettings } from './output';
3
+ export type Session = {
4
+ whenIdle: () => Promise<void>;
5
+ };
6
+ /**
7
+ * Open the prompt: read queries line by line, run each one once it is complete, until /exit, Ctrl+C or the end of
8
+ * piped input. Piped input (`jfsq < seed.txt`, stdin not a terminal) runs the same way, but quietly: no banner, no
9
+ * prompts and no goodbye, only the results and errors, and the exit code is 1 when any query failed.
10
+ * @param options The database options
11
+ * @param settings The output format and markers (the /output, /null and /missing commands change them)
12
+ * @returns {Session}
13
+ */
14
+ export declare const startInteractive: (options: DatabaseOptions, settings: OutputSettings) => Session;
15
+ //# sourceMappingURL=interactive.d.ts.map
@@ -0,0 +1,257 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.startInteractive = void 0;
7
+ require("core-js/modules/es.array.includes.js");
8
+ var readline = _interopRequireWildcard(require("readline"));
9
+ var _util = require("util");
10
+ var _queryStatus = require("../data/queries/queryStatus");
11
+ var _query = require("../data/queries/query");
12
+ var _config = require("../data/utilities/config");
13
+ var _chooseDatabase = require("./chooseDatabase");
14
+ var _formatResult = require("./formatResult");
15
+ var _output = require("./output");
16
+ var e;
17
+ function _interopRequireWildcard(t, r) { if (!r && t && t.__esModule) return t; var n, a, o = Object.defineProperty, f = { __proto__: null, default: t }; if (Object(t) !== t) return f; if (e || "function" != typeof WeakMap || (e = new WeakMap()), e) { if (e.has(t)) return e.get(t); e.set(t, f); } for (a in t) "default" !== a && {}.hasOwnProperty.call(t, a) && ((n = o && Object.getOwnPropertyDescriptor(t, a)) && (n.get || n.set) ? o(f, a, n) : f[a] = t[a]); return f; }
18
+ const defaultPrompt = 'JSON-Query> ';
19
+ // A CLI command: a slash and a word, then its value (a query never starts like this; a `/*` comment has no word)
20
+ const commandMatch = /^\/([a-z]+)(?:\s+(.*))?$/i;
21
+ /**
22
+ * Whether text holds anything to run: something other than whitespace and comments.
23
+ * @param text
24
+ * @returns {boolean}
25
+ */
26
+ const hasQuery = text => text.replace(/\/\*[\s\S]*?\*\//g, '').replace(/--[^\n]*/g, '').trim() !== '';
27
+ /**
28
+ * The main prompt: the name of the database in use when there is one (it changes with `use`), otherwise JSON-Query.
29
+ * @returns {string}
30
+ */
31
+ const mainPrompt = () => {
32
+ const database = (0, _config.getSetting)('database', undefined);
33
+ return database ? `${database}> ` : defaultPrompt;
34
+ };
35
+ // Continuation prompt, padded to line up with the first line and dimmed so it reads as secondary
36
+ const continuePrefix = waitingFor => (0, _util.styleText)('dim', `${waitingFor}> `.padStart(mainPrompt().length));
37
+ /**
38
+ * Open the prompt: read queries line by line, run each one once it is complete, until /exit, Ctrl+C or the end of
39
+ * piped input. Piped input (`jfsq < seed.txt`, stdin not a terminal) runs the same way, but quietly: no banner, no
40
+ * prompts and no goodbye, only the results and errors, and the exit code is 1 when any query failed.
41
+ * @param options The database options
42
+ * @param settings The output format and markers (the /output, /null and /missing commands change them)
43
+ * @returns {Session}
44
+ */
45
+ const startInteractive = (options, settings) => {
46
+ // Reading a file or a pipe rather than a person typing
47
+ const piped = !process.stdin.isTTY;
48
+ // Create the interface for reading from standard input/output
49
+ const rl = readline.createInterface({
50
+ input: process.stdin,
51
+ output: process.stdout,
52
+ prompt: defaultPrompt
53
+ });
54
+ // 1 when the session can't start (a bad option), so a script can tell
55
+ let exitCode = 0;
56
+ // Set when a query fails: piped input then exits 1, so a seed script can tell (the prompt carries on regardless)
57
+ let failed = false;
58
+ // Buffer to store multi-line inputs
59
+ let inputBuffer = '';
60
+ // Set once readline closes. Piped input closes it as soon as stdin ends, while queued lines may still be running,
61
+ // and readline throws if prompted after that
62
+ let closed = false;
63
+ /**
64
+ * Show the prompt, unless the session has already closed.
65
+ */
66
+ const showPrompt = () => {
67
+ if (!closed && !piped) {
68
+ rl.prompt();
69
+ }
70
+ };
71
+ // Lines are handled one at a time, in order: readline does not wait for an async 'line' handler, so without this
72
+ // queue a line typed (or piped) while a query runs would be appended to the buffer before that query finished.
73
+ let lineQueue = Promise.resolve();
74
+ const whenIdle = async () => {
75
+ // A handled line can queue more work (e.g. /exit queues the goodbye), so wait until the queue stops growing
76
+ let current;
77
+ do {
78
+ current = lineQueue;
79
+ await current;
80
+ } while (current !== lineQueue);
81
+ };
82
+ if (!piped) {
83
+ console.log('JSON Filesystem Query CLI');
84
+ console.log('Type your command. End it with a semicolon (;) and press Enter to execute.');
85
+ console.log('Type "/exit" or press Ctrl+C to quit.');
86
+ }
87
+ /**
88
+ * Print a confirmation for a person typing; piped input stays quiet, so its output is only results (e.g. for jq).
89
+ * @param {string} message
90
+ */
91
+ const say = message => {
92
+ if (!piped) {
93
+ console.log(message);
94
+ }
95
+ };
96
+ /**
97
+ * Run one CLI command. Without a value each one says what it is set to now.
98
+ * @param {string} line The trimmed line, e.g. `/output csv` or `/null (null)`
99
+ */
100
+ const runCommand = line => {
101
+ const [, name, value = ''] = line.match(commandMatch);
102
+ switch (name.toLowerCase()) {
103
+ case 'output':
104
+ {
105
+ const outputFormat = value.toLowerCase();
106
+ if (value && !_output.outputFormats.includes(outputFormat)) {
107
+ failed = true;
108
+ (0, _output.printError)(`Unknown output "${value}": use ${_output.outputFormats.join(', ')}`);
109
+ return;
110
+ }
111
+ if (value) {
112
+ settings.format = outputFormat;
113
+ }
114
+ say(`Output: ${settings.format}`);
115
+ return;
116
+ }
117
+ case 'null':
118
+ case 'missing':
119
+ {
120
+ const marker = name.toLowerCase();
121
+ if (value) {
122
+ settings.markers = {
123
+ ...settings.markers,
124
+ [marker]: value
125
+ };
126
+ }
127
+ say(`${marker === 'null' ? 'Null' : 'Missing'} shows as: ${settings.markers[marker] ?? _formatResult.defaultMarkers[marker]}`);
128
+ return;
129
+ }
130
+ default:
131
+ failed = true;
132
+ (0, _output.printError)(`Unknown command /${name}: use /output <${_output.outputFormats.join('|')}>, /null <text>, /missing <text> or /exit`);
133
+ }
134
+ };
135
+ /**
136
+ * Run a completed buffer through the query parser. A parse or runtime error is printed and the session carries on.
137
+ * @param {string} input The completed query buffer (one or more queries)
138
+ * @returns {Promise<void>}
139
+ */
140
+ const processInput = async input => {
141
+ try {
142
+ (0, _output.assertPrintable)(settings);
143
+ (0, _output.printResults)(await (0, _query.query)(input), settings);
144
+ } catch (error) {
145
+ failed = true;
146
+ (0, _output.printError)(error);
147
+ }
148
+ };
149
+ /**
150
+ * Handle one line of input: exit, skip, add to the buffer, or run the buffer once the query is complete.
151
+ * @param {string} line The line as typed, without its line ending
152
+ * @returns {Promise<void>}
153
+ */
154
+ const handleLine = async line => {
155
+ // The session didn't start (a bad option): lines piped after it must not run against the wrong database
156
+ if (exitCode) {
157
+ return;
158
+ }
159
+ const trimmedLine = line.trim();
160
+ // Check if the user wants to exit
161
+ if (!inputBuffer && trimmedLine.toLowerCase() === '/exit') {
162
+ rl.close();
163
+ return;
164
+ }
165
+ // A CLI command (/output, /null, /missing) at the start of a query: never sent to the query parser
166
+ if (!inputBuffer && commandMatch.test(trimmedLine)) {
167
+ runCommand(trimmedLine);
168
+ showPrompt();
169
+ return;
170
+ }
171
+ // Skip blank lines on their own, accept them mid-query
172
+ if (!inputBuffer && !trimmedLine) {
173
+ showPrompt();
174
+ return;
175
+ }
176
+ // Append current line to the buffer
177
+ inputBuffer += (inputBuffer ? '\n' : '') + line;
178
+ const status = (0, _queryStatus.queryStatus)(inputBuffer);
179
+ // Check if the line ends the query (closed query and semicolon present)
180
+ if (status.complete) {
181
+ // Take the buffer before running, so the next query starts empty even if this one fails
182
+ const input = inputBuffer;
183
+ inputBuffer = '';
184
+ await processInput(input);
185
+ // After the query, so a `use` in it shows in the prompt
186
+ rl.setPrompt(mainPrompt());
187
+ } else {
188
+ // Change prompt visually to show it is expecting continuation
189
+ rl.setPrompt(continuePrefix(status.waitingFor));
190
+ }
191
+ showPrompt();
192
+ };
193
+ // Handle line-by-line input, queued so each line waits for the one before it
194
+ rl.on('line', line => {
195
+ lineQueue = lineQueue.then(() => handleLine(line))
196
+ // Never let one failed line stop the queue (query errors are already handled in processInput)
197
+ .catch(error => {
198
+ (0, _output.printError)(error);
199
+ showPrompt();
200
+ });
201
+ });
202
+ rl.on('SIGINT', () => {
203
+ if (!inputBuffer && !rl.line) {
204
+ rl.close();
205
+ return;
206
+ }
207
+ rl.write(null, {
208
+ ctrl: true,
209
+ name: 'u'
210
+ });
211
+ inputBuffer = '';
212
+ rl.setPrompt(mainPrompt());
213
+ showPrompt();
214
+ });
215
+ // Handle graceful exit on Ctrl+C, /exit or the end of piped input, after any queued lines have run
216
+ rl.on('close', () => {
217
+ closed = true;
218
+ lineQueue = lineQueue.then(async () => {
219
+ // A file whose last query has no closing `;` still runs it (unless all that's left is comments or space)
220
+ if (piped && !exitCode && hasQuery(inputBuffer)) {
221
+ const input = inputBuffer;
222
+ inputBuffer = '';
223
+ await processInput(input);
224
+ }
225
+ if (!exitCode && !piped) {
226
+ console.log('\nGoodbye!');
227
+ }
228
+ process.exit(exitCode || (piped && failed ? 1 : 0));
229
+ });
230
+ });
231
+ /**
232
+ * Point the session at the database the options name, then open the prompt. A bad option (an invalid name, a
233
+ * database that doesn't exist) prints the error and ends the session with exit code 1.
234
+ * @returns {Promise<void>}
235
+ */
236
+ const start = async () => {
237
+ try {
238
+ const database = await (0, _chooseDatabase.chooseDatabase)(options);
239
+ if (!piped) {
240
+ console.log(`${database}\n`);
241
+ }
242
+ } catch (error) {
243
+ (0, _output.printError)(error);
244
+ exitCode = 1;
245
+ rl.close();
246
+ return;
247
+ }
248
+ rl.setPrompt(mainPrompt());
249
+ showPrompt();
250
+ };
251
+ // Lines typed before the session is ready wait for it
252
+ lineQueue = start();
253
+ return {
254
+ whenIdle
255
+ };
256
+ };
257
+ exports.startInteractive = startInteractive;
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.startInteractive=void 0,require("core-js/modules/es.array.includes.js");var e,readline=_interopRequireWildcard(require("readline")),_util=require("util"),_queryStatus=require("../data/queries/queryStatus"),_query=require("../data/queries/query"),_config=require("../data/utilities/config"),_chooseDatabase=require("./chooseDatabase"),_formatResult=require("./formatResult"),_output=require("./output");function _interopRequireWildcard(t,r){if(!r&&t&&t.__esModule)return t;var o,s,a=Object.defineProperty,n={__proto__:null,default:t};if(Object(t)!==t)return n;if(e||"function"!=typeof WeakMap||(e=new WeakMap),e){if(e.has(t))return e.get(t);e.set(t,n)}for(s in t)"default"!==s&&{}.hasOwnProperty.call(t,s)&&((o=a&&Object.getOwnPropertyDescriptor(t,s))&&(o.get||o.set)?a(n,s,o):n[s]=t[s]);return n}const defaultPrompt="JSON-Query> ",commandMatch=/^\/([a-z]+)(?:\s+(.*))?$/i,hasQuery=t=>""!==t.replace(/\/\*[\s\S]*?\*\//g,"").replace(/--[^\n]*/g,"").trim(),mainPrompt=()=>{const t=(0,_config.getSetting)("database",void 0);return t?`${t}> `:defaultPrompt},continuePrefix=t=>(0,_util.styleText)("dim",`${t}> `.padStart(mainPrompt().length)),startInteractive=(t,e)=>{const r=!process.stdin.isTTY,o=readline.createInterface({input:process.stdin,output:process.stdout,prompt:defaultPrompt});let s=0,a=!1,n="",u=!1;const i=()=>{u||r||o.prompt()};let c=Promise.resolve();r||(console.log("JSON Filesystem Query CLI"),console.log("Type your command. End it with a semicolon (;) and press Enter to execute."),console.log('Type "/exit" or press Ctrl+C to quit.'));const l=t=>{r||console.log(t)},p=async t=>{try{(0,_output.assertPrintable)(e),(0,_output.printResults)(await(0,_query.query)(t),e)}catch(t){a=!0,(0,_output.printError)(t)}},m=async t=>{if(s)return;const r=t.trim();if(!n&&"/exit"===r.toLowerCase())return void o.close();if(!n&&commandMatch.test(r))return(t=>{const[,r,o=""]=t.match(commandMatch);switch(r.toLowerCase()){case"output":{const t=o.toLowerCase();return o&&!_output.outputFormats.includes(t)?(a=!0,void(0,_output.printError)(`Unknown output "${o}": use ${_output.outputFormats.join(", ")}`)):(o&&(e.format=t),void l(`Output: ${e.format}`))}case"null":case"missing":{const t=r.toLowerCase();return o&&(e.markers={...e.markers,[t]:o}),void l(`${"null"===t?"Null":"Missing"} shows as: ${e.markers[t]??_formatResult.defaultMarkers[t]}`)}default:a=!0,(0,_output.printError)(`Unknown command /${r}: use /output <${_output.outputFormats.join("|")}>, /null <text>, /missing <text> or /exit`)}})(r),void i();if(!n&&!r)return void i();n+=(n?"\n":"")+t;const u=(0,_queryStatus.queryStatus)(n);if(u.complete){const t=n;n="",await p(t),o.setPrompt(mainPrompt())}else o.setPrompt(continuePrefix(u.waitingFor));i()};o.on("line",(t=>{c=c.then((()=>m(t))).catch((t=>{(0,_output.printError)(t),i()}))})),o.on("SIGINT",(()=>{n||o.line?(o.write(null,{ctrl:!0,name:"u"}),n="",o.setPrompt(mainPrompt()),i()):o.close()})),o.on("close",(()=>{u=!0,c=c.then((async()=>{if(r&&!s&&hasQuery(n)){const t=n;n="",await p(t)}s||r||console.log("\nGoodbye!"),process.exit(s||(r&&a?1:0))}))}));return c=(async()=>{try{const e=await(0,_chooseDatabase.chooseDatabase)(t);r||console.log(`${e}\n`)}catch(t){return(0,_output.printError)(t),s=1,void o.close()}o.setPrompt(mainPrompt()),i()})(),{whenIdle:async()=>{let t;do{t=c,await t}while(t!==c)}}};exports.startInteractive=startInteractive;