@graphscope-neug/darwin-arm64 0.1.2

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.
@@ -0,0 +1,310 @@
1
+ /** Copyright 2020 Alibaba Group Holding Limited.
2
+ *
3
+ * Licensed under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License.
5
+ * You may obtain a copy of the License at
6
+ *
7
+ * http://www.apache.org/licenses/LICENSE-2.0
8
+ *
9
+ * Unless required by applicable law or agreed to in writing, software
10
+ * distributed under the License is distributed on an "AS IS" BASIS,
11
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ * See the License for the specific language governing permissions and
13
+ * limitations under the License.
14
+ */
15
+
16
+ 'use strict';
17
+
18
+ const os = require('os');
19
+ const { Connection } = require('./connection');
20
+ const { AsyncConnection } = require('./async-connection');
21
+ const { readable } = require('./utils');
22
+ const { version } = require('./version');
23
+ const {
24
+ ERR_CONFIG_INVALID,
25
+ ERR_INVALID_ARGUMENT,
26
+ ERR_INVALID_PATH,
27
+ } = require('./error-codes');
28
+
29
+ // Load the native binding (unified loader handles prebuilds/ and build/)
30
+ const nativeBinding = require('./binding');
31
+
32
+ const ILLEGAL_CHARS = ['?', '*', '"', '<', '>', '|', ':', '\\'];
33
+ const PURE_MEMORY_PATHS = [':memory', ':memory:'];
34
+ const VALID_MODES = [
35
+ 'r', 'read', 'w', 'rw', 'write',
36
+ 'readwrite', 'read-write', 'read_write',
37
+ 'read-only', 'read_only',
38
+ ];
39
+
40
+ /**
41
+ * Database is the main entry point for the NeuG graph database.
42
+ *
43
+ * Use this class to open a database, create connections, and manage the database lifecycle.
44
+ *
45
+ * @example
46
+ * const { Database } = require('neug');
47
+ *
48
+ * const db = new Database({ databasePath: '/tmp/test.db', mode: 'w' });
49
+ * const conn = db.connect();
50
+ *
51
+ * conn.execute('CREATE (n:person {name: "Alice"})');
52
+ * const result = conn.execute('MATCH (n) RETURN n');
53
+ * for (const row of result) {
54
+ * console.log(row);
55
+ * }
56
+ *
57
+ * conn.close();
58
+ * db.close();
59
+ */
60
+ class Database {
61
+ /**
62
+ * Open a NeuG database.
63
+ *
64
+ * @param {Object} options - Database configuration options.
65
+ * @param {string|null} [options.databasePath=null] - Path to the database directory.
66
+ * Empty string or null for in-memory mode.
67
+ * @param {string} [options.mode='read-write'] - Database access mode.
68
+ * Supported: 'r', 'read', 'w', 'rw', 'write', 'readwrite', 'read-write', 'read-only'.
69
+ * @param {number} [options.maxThreadNum=0] - Maximum number of threads (0 = no limit).
70
+ * @param {boolean} [options.checkpointOnClose=true] - Whether to checkpoint on close.
71
+ * @param {string} [options.bufferStrategy='M_FULL'] - Buffer strategy:
72
+ * 'InMemory'/'M_FULL', 'SyncToFile'/'M_LAZY', 'HugePagePreferred'/'M_HUGE'.
73
+ */
74
+ constructor(options = {}) {
75
+ const {
76
+ databasePath = null,
77
+ mode = 'read-write',
78
+ maxThreadNum = 0,
79
+ checkpointOnClose = true,
80
+ bufferStrategy = 'M_FULL',
81
+ } = options;
82
+
83
+ this._connections = [];
84
+ this._asyncConnections = [];
85
+ this._serving = false;
86
+
87
+ // Validate path
88
+ const dbPath = databasePath !== null ? databasePath : '';
89
+ if (
90
+ typeof dbPath === 'string' &&
91
+ ILLEGAL_CHARS.some((ch) => dbPath.includes(ch)) &&
92
+ !PURE_MEMORY_PATHS.includes(dbPath)
93
+ ) {
94
+ throw new Error(
95
+ `Invalid path: database path '${dbPath}' contains illegal characters: ` +
96
+ `[${ILLEGAL_CHARS.join(', ')}]. Error code: ${ERR_INVALID_PATH}.`
97
+ );
98
+ }
99
+
100
+ // Validate mode
101
+ if (!VALID_MODES.includes(mode)) {
102
+ throw new Error(
103
+ `Invalid mode: ${mode}. Must be one of: ${VALID_MODES.join(', ')}. ` +
104
+ `Error code: ${ERR_INVALID_ARGUMENT}.`
105
+ );
106
+ }
107
+
108
+ // Validate maxThreadNum
109
+ if (maxThreadNum < 0) {
110
+ throw new Error(
111
+ `Invalid config: maxThreadNum: ${maxThreadNum}. Must be a non-negative integer. ` +
112
+ `Error code: ${ERR_CONFIG_INVALID}.`
113
+ );
114
+ }
115
+
116
+ const cpuCount = os.cpus().length;
117
+ if (maxThreadNum > cpuCount) {
118
+ throw new Error(
119
+ `Invalid argument: maxThreadNum: ${maxThreadNum}. ` +
120
+ `Must be less than or equal to CPU cores: ${cpuCount}. ` +
121
+ `Error code: ${ERR_INVALID_ARGUMENT}.`
122
+ );
123
+ }
124
+
125
+ // In-memory database cannot be opened in read-only mode
126
+ // NOTE: only null (i.e. databasePath not provided) is blocked;
127
+ // empty string '' is allowed, matching the Python binding behaviour.
128
+ if (
129
+ databasePath === null &&
130
+ ['r', 'read', 'read-only', 'read_only'].includes(mode)
131
+ ) {
132
+ throw new Error(
133
+ `Invalid mode: ${mode}. In-memory database cannot be opened in read-only mode. ` +
134
+ `Error code: ${ERR_INVALID_ARGUMENT}.`
135
+ );
136
+ }
137
+
138
+ this._dbPath = dbPath;
139
+ this._mode = mode;
140
+ this._maxThreadNum = maxThreadNum;
141
+
142
+ // Create the native database
143
+ this._database = new nativeBinding.NodeDatabase({
144
+ databasePath: dbPath,
145
+ maxThreadNum,
146
+ mode: readable(mode),
147
+ planner: 'gopt',
148
+ checkpointOnClose,
149
+ bufferStrategy,
150
+ });
151
+
152
+ if (!dbPath || dbPath.trim() === '') {
153
+ console.log(`[neug] Open in-memory database in ${readable(mode)} mode`);
154
+ } else {
155
+ console.log(`[neug] Open database ${dbPath} in ${mode} mode`);
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Get the version of the database.
161
+ * @returns {string} The version string.
162
+ */
163
+ get version() {
164
+ return version;
165
+ }
166
+
167
+ /**
168
+ * Get the database mode.
169
+ * @returns {string} The mode string.
170
+ */
171
+ get mode() {
172
+ return this._mode;
173
+ }
174
+
175
+ /**
176
+ * Connect to the database and return a Connection object.
177
+ *
178
+ * @returns {Connection} A connection to the database.
179
+ * @throws {Error} If the database is closed or serving.
180
+ */
181
+ connect() {
182
+ if (!this._database) {
183
+ throw new Error('Database is closed.');
184
+ }
185
+ if (this._serving) {
186
+ throw new Error(
187
+ 'Cannot create connection while the database server is running.'
188
+ );
189
+ }
190
+ const nativeConn = this._database.connect();
191
+ const conn = new Connection(nativeConn);
192
+ this._connections.push(conn);
193
+ return conn;
194
+ }
195
+
196
+ /**
197
+ * Connect to the database asynchronously.
198
+ *
199
+ * @returns {AsyncConnection} An async connection to the database.
200
+ * @throws {Error} If the database is closed or serving.
201
+ */
202
+ asyncConnect() {
203
+ if (!this._database) {
204
+ throw new Error('Database is closed.');
205
+ }
206
+ if (this._serving) {
207
+ throw new Error(
208
+ 'Cannot create async connection while the database server is running.'
209
+ );
210
+ }
211
+ const nativeConn = this._database.connect();
212
+ const asyncConn = new AsyncConnection(nativeConn);
213
+ this._asyncConnections.push(asyncConn);
214
+ return asyncConn;
215
+ }
216
+
217
+ /**
218
+ * Start the database server for handling remote connections (TP mode).
219
+ *
220
+ * @param {Object} [options] - Server options.
221
+ * @param {number} [options.port=10000] - The port to listen on.
222
+ * @param {string} [options.host='localhost'] - The host to bind to.
223
+ * @param {boolean} [options.blocking=true] - Whether to block until server stops.
224
+ * @returns {string} The URI of the server.
225
+ */
226
+ serve(options = {}) {
227
+ const { port = 10000, host = 'localhost', blocking = true } = options;
228
+
229
+ // Check all connections are closed
230
+ for (const conn of this._connections) {
231
+ if (conn && conn.isOpen) {
232
+ throw new Error(
233
+ 'Cannot start the server while there are open connections to the local database.'
234
+ );
235
+ }
236
+ }
237
+ for (const asyncConn of this._asyncConnections) {
238
+ if (asyncConn && asyncConn.isOpen) {
239
+ throw new Error(
240
+ 'Cannot start the server while there are open async connections to the local database.'
241
+ );
242
+ }
243
+ }
244
+
245
+ if (this._serving) {
246
+ console.log('[neug] Database server is already running.');
247
+ return;
248
+ }
249
+ this._serving = true;
250
+ console.log(`[neug] Starting database server on ${host}:${port}.`);
251
+
252
+ try {
253
+ return this._database.serve(port, host, this._maxThreadNum, blocking);
254
+ } catch (e) {
255
+ this._serving = false;
256
+ throw e;
257
+ }
258
+ }
259
+
260
+ /**
261
+ * Stop the database server.
262
+ * @throws {Error} If the server is not running.
263
+ */
264
+ stopServing() {
265
+ if (!this._serving) {
266
+ throw new Error('Database server is not running.');
267
+ }
268
+ console.log('[neug] Stopping database server.');
269
+ this._database.stopServing();
270
+ this._serving = false;
271
+ }
272
+
273
+ /**
274
+ * Close the database connection and release all resources.
275
+ */
276
+ close() {
277
+ if (this._dbPath && this._dbPath.trim() !== '') {
278
+ console.log(`[neug] Closing database ${this._dbPath}.`);
279
+ }
280
+
281
+ // Close all connections
282
+ if (this._connections) {
283
+ for (const conn of this._connections) {
284
+ try {
285
+ conn.close();
286
+ } catch (e) {
287
+ console.warn(`[neug] Failed to close connection: ${e.message}`);
288
+ }
289
+ }
290
+ }
291
+
292
+ // Close all async connections
293
+ if (this._asyncConnections) {
294
+ for (const asyncConn of this._asyncConnections) {
295
+ try {
296
+ asyncConn.close();
297
+ } catch (e) {
298
+ console.warn(`[neug] Failed to close async connection: ${e.message}`);
299
+ }
300
+ }
301
+ }
302
+
303
+ if (this._database) {
304
+ this._database.close();
305
+ this._database = null;
306
+ }
307
+ }
308
+ }
309
+
310
+ module.exports = { Database };
@@ -0,0 +1,208 @@
1
+ /** Copyright 2020 Alibaba Group Holding Limited.
2
+ *
3
+ * Licensed under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License.
5
+ * You may obtain a copy of the License at
6
+ *
7
+ * http://www.apache.org/licenses/LICENSE-2.0
8
+ *
9
+ * Unless required by applicable law or agreed to in writing, software
10
+ * distributed under the License is distributed on an "AS IS" BASIS,
11
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ * See the License for the specific language governing permissions and
13
+ * limitations under the License.
14
+ */
15
+
16
+ /**
17
+ * Error codes mirror the protobuf `Code` enum defined in `proto/error.proto`.
18
+ * Provides both numeric constants and a reverse mapping from code to name.
19
+ *
20
+ * @module error-codes
21
+ */
22
+
23
+ 'use strict';
24
+
25
+ // ---- Numeric error code constants (from error.proto Code enum) ----
26
+ // General errors (1001-1017)
27
+ const OK = 0;
28
+ const ERR_PERMISSION = 1001;
29
+ const ERR_VERSION_MISMATCHED = 1002;
30
+ const ERR_DIRECTORY_NOT_EXIST = 1003;
31
+ const ERR_DATABASE_LOCKED = 1004;
32
+ const ERR_DISK_SPACE_EXHAUSTED = 1005;
33
+ const ERR_CORRUPTION_DETECTED = 1006;
34
+ const ERR_INVALID_PATH = 1007;
35
+ const ERR_CONFIG_INVALID = 1008;
36
+ const ERR_INVALID_ARGUMENT = 1009;
37
+ const ERR_NOT_FOUND = 1010;
38
+ const ERR_NOT_SUPPORTED = 1011;
39
+ const ERR_INTERNAL_ERROR = 1012;
40
+ const ERR_ILLEGAL_OPERATION = 1013;
41
+ const ERR_IO_ERROR = 1014;
42
+ const ERR_BAD_ENCODING = 1015;
43
+ const ERR_INVALID_FILE = 1016;
44
+ const ERR_EXTENSION = 1017;
45
+
46
+ // Network errors (2001-2007)
47
+ const ERR_NETWORK = 2001;
48
+ const ERR_SESSION_CLOSED = 2002;
49
+ const ERR_CONNECTION_CLOSED = 2003;
50
+ const ERR_POOL_EXHAUSTED = 2004;
51
+ const ERR_SERVICE_UNAVAILABLE = 2005;
52
+ const ERR_LOAD_OVERFLOW = 2006;
53
+ const ERR_CONNECTION_ERROR = 2007;
54
+
55
+ // Query errors (3000-3007)
56
+ const ERR_COMPILATION = 3000;
57
+ const ERR_QUERY_EXECUTION = 3001;
58
+ const ERR_QUERY_SYNTAX = 3002;
59
+ const ERR_QUERY_TIMEOUT = 3003;
60
+ const ERR_CONCURRENT_WRITE = 3004;
61
+ const ERR_CODEGEN_ERROR = 3005;
62
+ const ERR_EMPTY_RESULT = 3006;
63
+ const ERR_NOT_INITIALIZED = 3007;
64
+
65
+ // Transaction errors (4001-4003)
66
+ const ERR_TX_STATE_CONFLICT = 4001;
67
+ const ERR_WAL_WRITE_FAIL = 4002;
68
+ const ERR_TX_TIMEOUT = 4003;
69
+
70
+ // Schema errors (5001-5005)
71
+ const ERR_SCHEMA_MISMATCH = 5001;
72
+ const ERR_INVALID_SCHEMA = 5002;
73
+ const ERR_TYPE_CONVERSION = 5003;
74
+ const ERR_TYPE_OVERFLOW = 5004;
75
+ const ERR_INDEX_ERROR = 5005;
76
+
77
+ // Deployment errors (6001-6004)
78
+ const ERR_PLATFORM_ABI = 6001;
79
+ const ERR_PY_BIND_INIT = 6002;
80
+ const ERR_ARCH_MISMATCH = 6003;
81
+ const ERR_DEPLOY_DEPENDENCY = 6004;
82
+
83
+ // Not implemented (7001)
84
+ const ERR_NOT_IMPLEMENTED = 7001;
85
+
86
+ // Unknown (9999)
87
+ const ERR_UNKNOWN = 9999;
88
+
89
+ // ---- Code enum: maps numeric code → name string ----
90
+ const Code = {
91
+ [OK]: 'OK',
92
+ [ERR_PERMISSION]: 'ERR_PERMISSION',
93
+ [ERR_VERSION_MISMATCHED]: 'ERR_VERSION_MISMATCHED',
94
+ [ERR_DIRECTORY_NOT_EXIST]: 'ERR_DIRECTORY_NOT_EXIST',
95
+ [ERR_DATABASE_LOCKED]: 'ERR_DATABASE_LOCKED',
96
+ [ERR_DISK_SPACE_EXHAUSTED]: 'ERR_DISK_SPACE_EXHAUSTED',
97
+ [ERR_CORRUPTION_DETECTED]: 'ERR_CORRUPTION_DETECTED',
98
+ [ERR_INVALID_PATH]: 'ERR_INVALID_PATH',
99
+ [ERR_CONFIG_INVALID]: 'ERR_CONFIG_INVALID',
100
+ [ERR_INVALID_ARGUMENT]: 'ERR_INVALID_ARGUMENT',
101
+ [ERR_NOT_FOUND]: 'ERR_NOT_FOUND',
102
+ [ERR_NOT_SUPPORTED]: 'ERR_NOT_SUPPORTED',
103
+ [ERR_INTERNAL_ERROR]: 'ERR_INTERNAL_ERROR',
104
+ [ERR_ILLEGAL_OPERATION]: 'ERR_ILLEGAL_OPERATION',
105
+ [ERR_IO_ERROR]: 'ERR_IO_ERROR',
106
+ [ERR_BAD_ENCODING]: 'ERR_BAD_ENCODING',
107
+ [ERR_INVALID_FILE]: 'ERR_INVALID_FILE',
108
+ [ERR_EXTENSION]: 'ERR_EXTENSION',
109
+ [ERR_NETWORK]: 'ERR_NETWORK',
110
+ [ERR_SESSION_CLOSED]: 'ERR_SESSION_CLOSED',
111
+ [ERR_CONNECTION_CLOSED]: 'ERR_CONNECTION_CLOSED',
112
+ [ERR_POOL_EXHAUSTED]: 'ERR_POOL_EXHAUSTED',
113
+ [ERR_SERVICE_UNAVAILABLE]: 'ERR_SERVICE_UNAVAILABLE',
114
+ [ERR_LOAD_OVERFLOW]: 'ERR_LOAD_OVERFLOW',
115
+ [ERR_CONNECTION_ERROR]: 'ERR_CONNECTION_ERROR',
116
+ [ERR_COMPILATION]: 'ERR_COMPILATION',
117
+ [ERR_QUERY_EXECUTION]: 'ERR_QUERY_EXECUTION',
118
+ [ERR_QUERY_SYNTAX]: 'ERR_QUERY_SYNTAX',
119
+ [ERR_QUERY_TIMEOUT]: 'ERR_QUERY_TIMEOUT',
120
+ [ERR_CONCURRENT_WRITE]: 'ERR_CONCURRENT_WRITE',
121
+ [ERR_CODEGEN_ERROR]: 'ERR_CODEGEN_ERROR',
122
+ [ERR_EMPTY_RESULT]: 'ERR_EMPTY_RESULT',
123
+ [ERR_NOT_INITIALIZED]: 'ERR_NOT_INITIALIZED',
124
+ [ERR_TX_STATE_CONFLICT]: 'ERR_TX_STATE_CONFLICT',
125
+ [ERR_WAL_WRITE_FAIL]: 'ERR_WAL_WRITE_FAIL',
126
+ [ERR_TX_TIMEOUT]: 'ERR_TX_TIMEOUT',
127
+ [ERR_SCHEMA_MISMATCH]: 'ERR_SCHEMA_MISMATCH',
128
+ [ERR_INVALID_SCHEMA]: 'ERR_INVALID_SCHEMA',
129
+ [ERR_TYPE_CONVERSION]: 'ERR_TYPE_CONVERSION',
130
+ [ERR_TYPE_OVERFLOW]: 'ERR_TYPE_OVERFLOW',
131
+ [ERR_INDEX_ERROR]: 'ERR_INDEX_ERROR',
132
+ [ERR_PLATFORM_ABI]: 'ERR_PLATFORM_ABI',
133
+ [ERR_PY_BIND_INIT]: 'ERR_PY_BIND_INIT',
134
+ [ERR_ARCH_MISMATCH]: 'ERR_ARCH_MISMATCH',
135
+ [ERR_DEPLOY_DEPENDENCY]: 'ERR_DEPLOY_DEPENDENCY',
136
+ [ERR_NOT_IMPLEMENTED]: 'ERR_NOT_IMPLEMENTED',
137
+ [ERR_UNKNOWN]: 'ERR_UNKNOWN',
138
+ };
139
+
140
+ /**
141
+ * Get the name of an error code.
142
+ * @param {number} code - The numeric error code.
143
+ * @returns {string} The error code name, or 'ERR_UNKNOWN' if not found.
144
+ */
145
+ function codeName(code) {
146
+ return Code[code] || 'ERR_UNKNOWN';
147
+ }
148
+
149
+ module.exports = {
150
+ // General errors
151
+ OK,
152
+ ERR_PERMISSION,
153
+ ERR_VERSION_MISMATCHED,
154
+ ERR_DIRECTORY_NOT_EXIST,
155
+ ERR_DATABASE_LOCKED,
156
+ ERR_DISK_SPACE_EXHAUSTED,
157
+ ERR_CORRUPTION_DETECTED,
158
+ ERR_INVALID_PATH,
159
+ ERR_CONFIG_INVALID,
160
+ ERR_INVALID_ARGUMENT,
161
+ ERR_NOT_FOUND,
162
+ ERR_NOT_SUPPORTED,
163
+ ERR_INTERNAL_ERROR,
164
+ ERR_ILLEGAL_OPERATION,
165
+ ERR_IO_ERROR,
166
+ ERR_BAD_ENCODING,
167
+ ERR_INVALID_FILE,
168
+ ERR_EXTENSION,
169
+ // Network errors
170
+ ERR_NETWORK,
171
+ ERR_SESSION_CLOSED,
172
+ ERR_CONNECTION_CLOSED,
173
+ ERR_POOL_EXHAUSTED,
174
+ ERR_SERVICE_UNAVAILABLE,
175
+ ERR_LOAD_OVERFLOW,
176
+ ERR_CONNECTION_ERROR,
177
+ // Query errors
178
+ ERR_COMPILATION,
179
+ ERR_QUERY_EXECUTION,
180
+ ERR_QUERY_SYNTAX,
181
+ ERR_QUERY_TIMEOUT,
182
+ ERR_CONCURRENT_WRITE,
183
+ ERR_CODEGEN_ERROR,
184
+ ERR_EMPTY_RESULT,
185
+ ERR_NOT_INITIALIZED,
186
+ // Transaction errors
187
+ ERR_TX_STATE_CONFLICT,
188
+ ERR_WAL_WRITE_FAIL,
189
+ ERR_TX_TIMEOUT,
190
+ // Schema errors
191
+ ERR_SCHEMA_MISMATCH,
192
+ ERR_INVALID_SCHEMA,
193
+ ERR_TYPE_CONVERSION,
194
+ ERR_TYPE_OVERFLOW,
195
+ ERR_INDEX_ERROR,
196
+ // Deployment errors
197
+ ERR_PLATFORM_ABI,
198
+ ERR_PY_BIND_INIT,
199
+ ERR_ARCH_MISMATCH,
200
+ ERR_DEPLOY_DEPENDENCY,
201
+ // Not implemented
202
+ ERR_NOT_IMPLEMENTED,
203
+ // Unknown
204
+ ERR_UNKNOWN,
205
+ // Code enum (code → name)
206
+ Code,
207
+ codeName,
208
+ };
package/lib/format.js ADDED
@@ -0,0 +1,131 @@
1
+ /** Copyright 2020 Alibaba Group Holding Limited.
2
+ *
3
+ * Licensed under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License.
5
+ * You may obtain a copy of the License at
6
+ *
7
+ * http://www.apache.org/licenses/LICENSE-2.0
8
+ *
9
+ * Unless required by applicable law or agreed to in writing, software
10
+ * distributed under the License is distributed on an "AS IS" BASIS,
11
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ * See the License for the specific language governing permissions and
13
+ * limitations under the License.
14
+ */
15
+
16
+ 'use strict';
17
+
18
+ /**
19
+ * Parse an individual entry value to a display string.
20
+ *
21
+ * @param {*} entry - The value to parse (could be object, array, or primitive).
22
+ * @returns {string} The formatted string representation.
23
+ */
24
+ function parseEntry(entry) {
25
+ if (entry === null || entry === undefined) {
26
+ return 'null';
27
+ }
28
+ if (typeof entry === 'object' && !Array.isArray(entry) && !(entry instanceof Date)) {
29
+ // Object: vertex, edge, property, map
30
+ const pairs = Object.entries(entry)
31
+ .map(([key, value]) => `${key}: ${parseEntry(value)}`)
32
+ .join(', ');
33
+ return `{${pairs}}`;
34
+ }
35
+ if (Array.isArray(entry)) {
36
+ // Array: path, list
37
+ return entry.map(parseEntry).join(', ');
38
+ }
39
+ // Primitives: bool, int, string, Date, float, etc.
40
+ return String(entry);
41
+ }
42
+
43
+ /**
44
+ * Calculate column widths for table formatting.
45
+ *
46
+ * @param {string[][]} rows - The table rows.
47
+ * @param {string[]} headers - The column headers.
48
+ * @returns {number[]} Array of column widths.
49
+ */
50
+ function calculateColumnWidths(rows, headers) {
51
+ const widths = headers.map((h) => h.length);
52
+ for (const row of rows) {
53
+ for (let i = 0; i < row.length; i++) {
54
+ const cellLen = String(row[i] ?? '').length;
55
+ if (cellLen > widths[i]) {
56
+ widths[i] = cellLen;
57
+ }
58
+ }
59
+ }
60
+ return widths;
61
+ }
62
+
63
+ /**
64
+ * Print results as a grid-formatted table (similar to Python's tabulate with tablefmt='grid').
65
+ *
66
+ * @param {string[]} headers - The column headers.
67
+ * @param {string[][]} rows - The table rows.
68
+ */
69
+ function printResultsAsTable(headers, rows) {
70
+ const widths = calculateColumnWidths(rows, headers);
71
+
72
+ // Build separator line: +------+------+
73
+ const separator = '+' + widths.map((w) => '-'.repeat(w + 2)).join('+') + '+';
74
+
75
+ // Build a row line: | val1 | val2 |
76
+ function formatRow(cells) {
77
+ return (
78
+ '| ' +
79
+ cells
80
+ .map((cell, i) => {
81
+ const s = String(cell ?? '');
82
+ return s.padEnd(widths[i]);
83
+ })
84
+ .join(' | ') +
85
+ ' |'
86
+ );
87
+ }
88
+
89
+ const lines = [];
90
+ lines.push(separator);
91
+ lines.push(formatRow(headers));
92
+ lines.push(separator);
93
+ for (const row of rows) {
94
+ lines.push(formatRow(row));
95
+ }
96
+ lines.push(separator);
97
+
98
+ console.log(lines.join('\n'));
99
+ }
100
+
101
+ /**
102
+ * Parse and format a QueryResult for printing.
103
+ *
104
+ * @param {import('./query-result').QueryResult} queryResult - The query result to format.
105
+ * @param {number} [maxRows=20] - Maximum number of rows to display.
106
+ */
107
+ function parseAndFormatResults(queryResult, maxRows = 20) {
108
+ const headers = queryResult.columnNames();
109
+ const rows = [];
110
+
111
+ const totalRecords = queryResult.length();
112
+ let displayCount = Math.min(maxRows, totalRecords);
113
+
114
+ for (const record of queryResult) {
115
+ const currentRow = [];
116
+ for (const column of record) {
117
+ currentRow.push(parseEntry(column));
118
+ }
119
+ rows.push(currentRow);
120
+ displayCount--;
121
+ if (displayCount <= 0) break;
122
+ }
123
+
124
+ if (totalRecords > maxRows && rows.length > 0) {
125
+ rows.push(Array(headers.length).fill('...'));
126
+ }
127
+
128
+ printResultsAsTable(headers, rows);
129
+ }
130
+
131
+ module.exports = { parseEntry, printResultsAsTable, parseAndFormatResults };
package/lib/index.js ADDED
@@ -0,0 +1,66 @@
1
+ /** Copyright 2020 Alibaba Group Holding Limited.
2
+ *
3
+ * Licensed under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License.
5
+ * You may obtain a copy of the License at
6
+ *
7
+ * http://www.apache.org/licenses/LICENSE-2.0
8
+ *
9
+ * Unless required by applicable law or agreed to in writing, software
10
+ * distributed under the License is distributed on an "AS IS" BASIS,
11
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ * See the License for the specific language governing permissions and
13
+ * limitations under the License.
14
+ */
15
+
16
+ /**
17
+ * NeuG Node.js Bindings
18
+ *
19
+ * A high-performance embedded graph database with Cypher query support.
20
+ *
21
+ * @example
22
+ * const { Database } = require('neug');
23
+ *
24
+ * // Open an in-memory database
25
+ * const db = new Database({ mode: 'w' });
26
+ * const conn = db.connect();
27
+ *
28
+ * // Create schema
29
+ * conn.execute('CREATE (n:person {name: "Alice", age: 30})');
30
+ *
31
+ * // Query
32
+ * const result = conn.execute('MATCH (n) RETURN n');
33
+ * for (const row of result) {
34
+ * console.log(row);
35
+ * }
36
+ *
37
+ * conn.close();
38
+ * db.close();
39
+ *
40
+ * @module neug
41
+ */
42
+
43
+ 'use strict';
44
+
45
+ const { Database } = require('./database');
46
+ const { Connection } = require('./connection');
47
+ const { AsyncConnection } = require('./async-connection');
48
+ const { QueryResult } = require('./query-result');
49
+ const { Session } = require('./session');
50
+ const { parseAndFormatResults, printResultsAsTable, parseEntry } = require('./format');
51
+ const { version } = require('./version');
52
+ const errorCodes = require('./error-codes');
53
+
54
+ module.exports = {
55
+ Database,
56
+ Connection,
57
+ AsyncConnection,
58
+ QueryResult,
59
+ Session,
60
+ parseAndFormatResults,
61
+ printResultsAsTable,
62
+ parseEntry,
63
+ version,
64
+ // Error codes (mirrors protobuf Code enum)
65
+ ...errorCodes,
66
+ };