jl-db-comp 0.1.0__py3-none-any.whl

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 (33) hide show
  1. jl_db_comp/__init__.py +36 -0
  2. jl_db_comp/_version.py +4 -0
  3. jl_db_comp/labextension/build_log.json +728 -0
  4. jl_db_comp/labextension/package.json +219 -0
  5. jl_db_comp/labextension/schemas/jl_db_comp/package.json.orig +214 -0
  6. jl_db_comp/labextension/schemas/jl_db_comp/plugin.json +27 -0
  7. jl_db_comp/labextension/static/lib_index_js.a0969ed73da70f2cc451.js +561 -0
  8. jl_db_comp/labextension/static/lib_index_js.a0969ed73da70f2cc451.js.map +1 -0
  9. jl_db_comp/labextension/static/remoteEntry.5763ae02737e035e938c.js +560 -0
  10. jl_db_comp/labextension/static/remoteEntry.5763ae02737e035e938c.js.map +1 -0
  11. jl_db_comp/labextension/static/style.js +4 -0
  12. jl_db_comp/labextension/static/style_index_js.5364c7419a6b9db5d727.js +508 -0
  13. jl_db_comp/labextension/static/style_index_js.5364c7419a6b9db5d727.js.map +1 -0
  14. jl_db_comp/routes.py +332 -0
  15. jl_db_comp/tests/__init__.py +1 -0
  16. jl_db_comp/tests/test_routes.py +49 -0
  17. jl_db_comp-0.1.0.data/data/etc/jupyter/jupyter_server_config.d/jl_db_comp.json +7 -0
  18. jl_db_comp-0.1.0.data/data/share/jupyter/labextensions/jl_db_comp/build_log.json +728 -0
  19. jl_db_comp-0.1.0.data/data/share/jupyter/labextensions/jl_db_comp/install.json +5 -0
  20. jl_db_comp-0.1.0.data/data/share/jupyter/labextensions/jl_db_comp/package.json +219 -0
  21. jl_db_comp-0.1.0.data/data/share/jupyter/labextensions/jl_db_comp/schemas/jl_db_comp/package.json.orig +214 -0
  22. jl_db_comp-0.1.0.data/data/share/jupyter/labextensions/jl_db_comp/schemas/jl_db_comp/plugin.json +27 -0
  23. jl_db_comp-0.1.0.data/data/share/jupyter/labextensions/jl_db_comp/static/lib_index_js.a0969ed73da70f2cc451.js +561 -0
  24. jl_db_comp-0.1.0.data/data/share/jupyter/labextensions/jl_db_comp/static/lib_index_js.a0969ed73da70f2cc451.js.map +1 -0
  25. jl_db_comp-0.1.0.data/data/share/jupyter/labextensions/jl_db_comp/static/remoteEntry.5763ae02737e035e938c.js +560 -0
  26. jl_db_comp-0.1.0.data/data/share/jupyter/labextensions/jl_db_comp/static/remoteEntry.5763ae02737e035e938c.js.map +1 -0
  27. jl_db_comp-0.1.0.data/data/share/jupyter/labextensions/jl_db_comp/static/style.js +4 -0
  28. jl_db_comp-0.1.0.data/data/share/jupyter/labextensions/jl_db_comp/static/style_index_js.5364c7419a6b9db5d727.js +508 -0
  29. jl_db_comp-0.1.0.data/data/share/jupyter/labextensions/jl_db_comp/static/style_index_js.5364c7419a6b9db5d727.js.map +1 -0
  30. jl_db_comp-0.1.0.dist-info/METADATA +440 -0
  31. jl_db_comp-0.1.0.dist-info/RECORD +33 -0
  32. jl_db_comp-0.1.0.dist-info/WHEEL +4 -0
  33. jl_db_comp-0.1.0.dist-info/licenses/LICENSE +29 -0
@@ -0,0 +1,561 @@
1
+ "use strict";
2
+ (self["webpackChunkjl_db_comp"] = self["webpackChunkjl_db_comp"] || []).push([["lib_index_js"],{
3
+
4
+ /***/ "./lib/api.js"
5
+ /*!********************!*\
6
+ !*** ./lib/api.js ***!
7
+ \********************/
8
+ (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
9
+
10
+ __webpack_require__.r(__webpack_exports__);
11
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
12
+ /* harmony export */ fetchPostgresCompletions: () => (/* binding */ fetchPostgresCompletions)
13
+ /* harmony export */ });
14
+ /* harmony import */ var _jupyterlab_services__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @jupyterlab/services */ "webpack/sharing/consume/default/@jupyterlab/services");
15
+ /* harmony import */ var _jupyterlab_services__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_jupyterlab_services__WEBPACK_IMPORTED_MODULE_0__);
16
+ /* harmony import */ var _request__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./request */ "./lib/request.js");
17
+
18
+
19
+ /**
20
+ * Fetch PostgreSQL table and column completions from the server.
21
+ *
22
+ * @param dbUrl - PostgreSQL connection string (optional if using env var)
23
+ * @param prefix - Optional prefix to filter completions
24
+ * @param schema - Database schema name (default: 'public')
25
+ * @param tableName - Optional table name to filter columns (only returns columns from this table)
26
+ * @param schemaOrTable - Ambiguous identifier that could be either a schema or table name (backend will determine)
27
+ * @param jsonbColumn - Optional JSONB column name to extract keys from
28
+ * @param jsonbPath - Optional JSONB path for nested key extraction
29
+ * @returns Array of completion items
30
+ */
31
+ async function fetchPostgresCompletions(dbUrl, prefix = '', schema = 'public', tableName, schemaOrTable, jsonbColumn, jsonbPath) {
32
+ try {
33
+ const params = new URLSearchParams();
34
+ if (dbUrl) {
35
+ params.append('db_url', encodeURIComponent(dbUrl));
36
+ }
37
+ if (prefix) {
38
+ params.append('prefix', prefix);
39
+ }
40
+ params.append('schema', schema);
41
+ if (tableName) {
42
+ params.append('table', tableName);
43
+ }
44
+ if (schemaOrTable) {
45
+ params.append('schema_or_table', schemaOrTable);
46
+ }
47
+ if (jsonbColumn) {
48
+ params.append('jsonb_column', jsonbColumn);
49
+ if (jsonbPath && jsonbPath.length > 0) {
50
+ params.append('jsonb_path', JSON.stringify(jsonbPath));
51
+ }
52
+ }
53
+ const endpoint = `completions?${params.toString()}`;
54
+ const response = await (0,_request__WEBPACK_IMPORTED_MODULE_1__.requestAPI)(endpoint, {
55
+ method: 'GET'
56
+ });
57
+ if (response.status === 'error') {
58
+ console.error('PostgreSQL completion error:', response.message);
59
+ return [];
60
+ }
61
+ // If JSONB keys requested, return only those
62
+ if (jsonbColumn && response.jsonbKeys) {
63
+ return response.jsonbKeys;
64
+ }
65
+ // Return appropriate results based on context
66
+ if (tableName || schemaOrTable) {
67
+ // If we have table context, prefer columns
68
+ return response.columns.length > 0 ? response.columns : response.tables;
69
+ }
70
+ return [...response.tables, ...response.columns];
71
+ }
72
+ catch (err) {
73
+ if (err instanceof _jupyterlab_services__WEBPACK_IMPORTED_MODULE_0__.ServerConnection.ResponseError) {
74
+ const status = err.response.status;
75
+ let detail = err.message;
76
+ if (typeof detail === 'string' &&
77
+ (detail.includes('<!DOCTYPE') || detail.includes('<html'))) {
78
+ detail = `HTML error page (${detail.substring(0, 100)}...)`;
79
+ }
80
+ console.error(`PostgreSQL completions API failed (${status}): ${detail}`);
81
+ }
82
+ else {
83
+ const msg = err instanceof Error ? err.message : 'Unknown error';
84
+ console.error(`PostgreSQL completions API failed: ${msg}`);
85
+ }
86
+ return [];
87
+ }
88
+ }
89
+
90
+
91
+ /***/ },
92
+
93
+ /***/ "./lib/index.js"
94
+ /*!**********************!*\
95
+ !*** ./lib/index.js ***!
96
+ \**********************/
97
+ (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
98
+
99
+ __webpack_require__.r(__webpack_exports__);
100
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
101
+ /* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
102
+ /* harmony export */ });
103
+ /* harmony import */ var _jupyterlab_completer__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @jupyterlab/completer */ "webpack/sharing/consume/default/@jupyterlab/completer");
104
+ /* harmony import */ var _jupyterlab_completer__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_jupyterlab_completer__WEBPACK_IMPORTED_MODULE_0__);
105
+ /* harmony import */ var _jupyterlab_settingregistry__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @jupyterlab/settingregistry */ "webpack/sharing/consume/default/@jupyterlab/settingregistry");
106
+ /* harmony import */ var _jupyterlab_settingregistry__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_jupyterlab_settingregistry__WEBPACK_IMPORTED_MODULE_1__);
107
+ /* harmony import */ var _provider__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./provider */ "./lib/provider.js");
108
+
109
+
110
+
111
+ /**
112
+ * Plugin ID constant.
113
+ */
114
+ const PLUGIN_ID = 'jl_db_comp:plugin';
115
+ /**
116
+ * Initialization data for the jl_db_comp extension.
117
+ *
118
+ * This plugin provides PostgreSQL table and column name completions
119
+ * in JupyterLab notebooks and editors when typing SQL queries.
120
+ */
121
+ const plugin = {
122
+ id: PLUGIN_ID,
123
+ description: 'A JupyterLab extension to complete db queries in jupyterlab notebooks',
124
+ autoStart: true,
125
+ requires: [_jupyterlab_completer__WEBPACK_IMPORTED_MODULE_0__.ICompletionProviderManager],
126
+ optional: [_jupyterlab_settingregistry__WEBPACK_IMPORTED_MODULE_1__.ISettingRegistry],
127
+ activate: (app, completionManager, settingRegistry) => {
128
+ let provider;
129
+ if (settingRegistry) {
130
+ settingRegistry
131
+ .load(PLUGIN_ID)
132
+ .then(settings => {
133
+ provider = new _provider__WEBPACK_IMPORTED_MODULE_2__.PostgresCompletionProvider(settings);
134
+ completionManager.registerProvider(provider);
135
+ console.log('JupyterLab extension jl_db_comp is activated!');
136
+ })
137
+ .catch(reason => {
138
+ console.error('Failed to load settings for jl_db_comp:', reason);
139
+ provider = new _provider__WEBPACK_IMPORTED_MODULE_2__.PostgresCompletionProvider();
140
+ completionManager.registerProvider(provider);
141
+ console.log('JupyterLab extension jl_db_comp is activated!');
142
+ });
143
+ }
144
+ else {
145
+ provider = new _provider__WEBPACK_IMPORTED_MODULE_2__.PostgresCompletionProvider();
146
+ completionManager.registerProvider(provider);
147
+ console.log('JupyterLab extension jl_db_comp is activated!');
148
+ }
149
+ }
150
+ };
151
+ /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (plugin);
152
+
153
+
154
+ /***/ },
155
+
156
+ /***/ "./lib/provider.js"
157
+ /*!*************************!*\
158
+ !*** ./lib/provider.js ***!
159
+ \*************************/
160
+ (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
161
+
162
+ __webpack_require__.r(__webpack_exports__);
163
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
164
+ /* harmony export */ PostgresCompletionProvider: () => (/* binding */ PostgresCompletionProvider)
165
+ /* harmony export */ });
166
+ /* harmony import */ var _api__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./api */ "./lib/api.js");
167
+
168
+ /**
169
+ * PostgreSQL completion provider for JupyterLab.
170
+ *
171
+ * Provides table and column name completions from PostgreSQL databases
172
+ * when editing SQL-like code in notebooks and editors.
173
+ */
174
+ class PostgresCompletionProvider {
175
+ /**
176
+ * Create a new PostgresCompletionProvider.
177
+ *
178
+ * @param settings - Optional settings registry to load database configuration
179
+ */
180
+ constructor(settings) {
181
+ this.identifier = 'jl_db_comp:postgres-completer';
182
+ this.renderer = null;
183
+ this._cache = new Map();
184
+ this._cacheTTL = 5 * 60 * 1000; // 5 minutes in milliseconds
185
+ this._settings = null;
186
+ this._dbUrl = '';
187
+ this._schema = 'public';
188
+ this._enabled = true;
189
+ /**
190
+ * SQL keywords that trigger completion.
191
+ */
192
+ this._sqlKeywords = [
193
+ 'select',
194
+ 'from',
195
+ 'join',
196
+ 'where',
197
+ 'insert',
198
+ 'update',
199
+ 'delete',
200
+ 'inner',
201
+ 'left',
202
+ 'right',
203
+ 'outer',
204
+ 'on',
205
+ 'group',
206
+ 'order',
207
+ 'by',
208
+ 'having',
209
+ 'into',
210
+ 'values',
211
+ 'set'
212
+ ];
213
+ if (settings) {
214
+ this._settings = settings;
215
+ this._loadSettings();
216
+ settings.changed.connect(() => {
217
+ this._loadSettings();
218
+ });
219
+ }
220
+ }
221
+ /**
222
+ * Load database configuration from settings.
223
+ */
224
+ _loadSettings() {
225
+ if (!this._settings) {
226
+ return;
227
+ }
228
+ this._dbUrl = this._settings.get('databaseUrl').composite;
229
+ this._schema = this._settings.get('schema').composite;
230
+ this._enabled = this._settings.get('enabled').composite;
231
+ }
232
+ /**
233
+ * Determine if completions should be shown in the current context.
234
+ *
235
+ * Checks for SQL keywords or context that suggests SQL code.
236
+ */
237
+ async isApplicable(context) {
238
+ if (!this._enabled) {
239
+ return false;
240
+ }
241
+ // Get editor content from context
242
+ const editor = context.editor;
243
+ if (!editor) {
244
+ return false;
245
+ }
246
+ const text = editor.model.sharedModel.getSource();
247
+ if (!text) {
248
+ return false;
249
+ }
250
+ const textLower = text.toLowerCase();
251
+ // Check if any SQL keyword is present
252
+ return this._sqlKeywords.some(keyword => textLower.includes(keyword));
253
+ }
254
+ /**
255
+ * Fetch completion items for the current context.
256
+ *
257
+ * Uses caching to minimize database calls.
258
+ */
259
+ async fetch(request, context) {
260
+ var _a;
261
+ if (!this._enabled) {
262
+ return { start: request.offset, end: request.offset, items: [] };
263
+ }
264
+ const { text, offset } = request;
265
+ // Extract context: schema, table, and prefix
266
+ const extracted = this._extractContext(text, offset);
267
+ // Create cache key that includes full context
268
+ let cacheKey;
269
+ if (extracted.jsonbColumn) {
270
+ // JSONB key completion: table.column->path
271
+ const pathStr = ((_a = extracted.jsonbPath) === null || _a === void 0 ? void 0 : _a.join('.')) || '';
272
+ const tablePrefix = extracted.schemaOrTable
273
+ ? `${extracted.schemaOrTable}.`
274
+ : '';
275
+ cacheKey =
276
+ `${tablePrefix}${extracted.jsonbColumn}->${pathStr}.${extracted.prefix}`.toLowerCase();
277
+ }
278
+ else if (extracted.schema && extracted.tableName) {
279
+ // schema.table.prefix
280
+ cacheKey =
281
+ `${extracted.schema}.${extracted.tableName}.${extracted.prefix}`.toLowerCase();
282
+ }
283
+ else if (extracted.schemaOrTable) {
284
+ // schema.prefix OR table.prefix (ambiguous)
285
+ cacheKey = `${extracted.schemaOrTable}.${extracted.prefix}`.toLowerCase();
286
+ }
287
+ else {
288
+ // just prefix
289
+ cacheKey = extracted.prefix.toLowerCase();
290
+ }
291
+ // Check cache first
292
+ const cached = this._getCached(cacheKey);
293
+ if (cached) {
294
+ return this._formatReply(cached, request.offset, extracted.prefix);
295
+ }
296
+ // Fetch from database
297
+ try {
298
+ const items = await (0,_api__WEBPACK_IMPORTED_MODULE_0__.fetchPostgresCompletions)(this._dbUrl || undefined, extracted.prefix, extracted.schema || this._schema, extracted.tableName, extracted.schemaOrTable, extracted.jsonbColumn, extracted.jsonbPath);
299
+ // Cache the results
300
+ this._cache.set(cacheKey, {
301
+ items,
302
+ timestamp: Date.now()
303
+ });
304
+ return this._formatReply(items, request.offset, extracted.prefix);
305
+ }
306
+ catch (error) {
307
+ console.error('Failed to fetch PostgreSQL completions:', error);
308
+ return { start: request.offset, end: request.offset, items: [] };
309
+ }
310
+ }
311
+ /**
312
+ * Extract context from the text: prefix being typed, optional table name, optional schema, and JSONB context.
313
+ *
314
+ * Detects patterns like:
315
+ * - "schema.table.col" → { schema: "schema", tableName: "table", prefix: "col" }
316
+ * - "schema.table." → { schema: "schema", tableName: "table", prefix: "" }
317
+ * - "schema.tab" → { schemaOrTable: "schema", prefix: "tab" }
318
+ * - "schema." → { schemaOrTable: "schema", prefix: "" }
319
+ * - "table.col" → { schemaOrTable: "table", prefix: "col" }
320
+ * - "table." → { schemaOrTable: "table", prefix: "" }
321
+ * - "prefix" → { prefix: "prefix" }
322
+ * - "column_name->" → { jsonbColumn: "column_name", jsonbPath: [], prefix: "" }
323
+ * - "column_name->>'key1'->" → { jsonbColumn: "column_name", jsonbPath: ["key1"], prefix: "" }
324
+ * - "table.column_name->>'key'->" → { schemaOrTable: "table", jsonbColumn: "column_name", jsonbPath: ["key"], prefix: "" }
325
+ *
326
+ * Note: For single-dot patterns (schema. or table.), the backend will determine
327
+ * whether it's a schema (list tables) or table (list columns) by checking the database.
328
+ */
329
+ _extractContext(text, offset) {
330
+ const beforeCursor = text.substring(0, offset);
331
+ // JSONB pattern: Detect -> or ->> operators
332
+ // Examples: metadata-> or content -> or patients.metadata->>'key'->
333
+ if (beforeCursor.includes('->')) {
334
+ // Much simpler approach: find the last -> or ->> and work backwards
335
+ // Look for: word characters, optional dot+word, then ->, then anything
336
+ // Pattern: (word.)?word -> rest
337
+ const simpleMatch = beforeCursor.match(/([\w]+\.)?([\w]+)\s*->\s*(.*)$/);
338
+ if (simpleMatch) {
339
+ const tableOrSchema = simpleMatch[1]
340
+ ? simpleMatch[1].slice(0, -1)
341
+ : undefined; // Remove trailing dot
342
+ const columnName = simpleMatch[2];
343
+ const afterOperator = simpleMatch[3];
344
+ // Parse the path after the first ->
345
+ // Example: "'key1'->>'key2'->" or "key1" or ""
346
+ const jsonbPath = [];
347
+ const pathRegex = /['"]?([\w]+)['"]?\s*->/g;
348
+ let pathMatch;
349
+ while ((pathMatch = pathRegex.exec(afterOperator)) !== null) {
350
+ jsonbPath.push(pathMatch[1]);
351
+ }
352
+ // Get the current prefix (what's being typed after the last ->)
353
+ // Remove any keys that are part of the path
354
+ const lastArrowIndex = afterOperator.lastIndexOf('->');
355
+ let currentPrefix = '';
356
+ if (lastArrowIndex >= 0) {
357
+ currentPrefix = afterOperator
358
+ .substring(lastArrowIndex + 2)
359
+ .trim()
360
+ .replace(/['"]/g, '');
361
+ }
362
+ else {
363
+ // No nested path, just get whatever is after the ->
364
+ currentPrefix = afterOperator.trim().replace(/['"]/g, '');
365
+ }
366
+ return {
367
+ schemaOrTable: tableOrSchema,
368
+ jsonbColumn: columnName,
369
+ jsonbPath,
370
+ prefix: currentPrefix
371
+ };
372
+ }
373
+ }
374
+ // Three-part pattern: schema.table.column
375
+ const threePartMatch = beforeCursor.match(/([\w]+)\.([\w]+)\.([\w]*)$/);
376
+ if (threePartMatch) {
377
+ return {
378
+ schema: threePartMatch[1],
379
+ tableName: threePartMatch[2],
380
+ prefix: threePartMatch[3]
381
+ };
382
+ }
383
+ // Two-part pattern: could be schema.table OR table.column
384
+ // Backend will determine which by checking if first part is a schema
385
+ const twoPartMatch = beforeCursor.match(/([\w]+)\.([\w]*)$/);
386
+ if (twoPartMatch) {
387
+ return {
388
+ schemaOrTable: twoPartMatch[1],
389
+ prefix: twoPartMatch[2]
390
+ };
391
+ }
392
+ // Single word: could be a table name OR a column name
393
+ // Check if there's a FROM clause in the query to determine context
394
+ const wordMatch = beforeCursor.match(/[\w]+$/);
395
+ const prefix = wordMatch ? wordMatch[0] : '';
396
+ // Look for FROM clause in the entire text (before or after cursor)
397
+ // Match patterns like: FROM table, FROM schema.table, FROM table AS alias
398
+ const fullText = text.toLowerCase();
399
+ const fromMatch = fullText.match(/\bfrom\s+([\w]+\.)?[\w]+/);
400
+ if (fromMatch) {
401
+ // Extract the table name (with optional schema)
402
+ const fromClause = fromMatch[0];
403
+ const tableMatch = fromClause.match(/\bfrom\s+(?:([\w]+)\.)?([\w]+)/);
404
+ if (tableMatch) {
405
+ const schema = tableMatch[1];
406
+ const table = tableMatch[2];
407
+ // If we have a schema, return schema.table pattern
408
+ if (schema) {
409
+ return {
410
+ schema,
411
+ tableName: table,
412
+ prefix
413
+ };
414
+ }
415
+ // Otherwise, return table as schemaOrTable (backend will check if it's a table)
416
+ return {
417
+ schemaOrTable: table,
418
+ prefix
419
+ };
420
+ }
421
+ }
422
+ // No FROM clause found, just return prefix (will suggest tables)
423
+ return {
424
+ prefix
425
+ };
426
+ }
427
+ /**
428
+ * Get cached completion items if still valid.
429
+ */
430
+ _getCached(prefix) {
431
+ const key = prefix.toLowerCase();
432
+ const entry = this._cache.get(key);
433
+ if (!entry) {
434
+ return null;
435
+ }
436
+ const age = Date.now() - entry.timestamp;
437
+ if (age > this._cacheTTL) {
438
+ this._cache.delete(key);
439
+ return null;
440
+ }
441
+ return entry.items;
442
+ }
443
+ /**
444
+ * Format completion items into the reply format expected by JupyterLab.
445
+ */
446
+ _formatReply(items, offset, prefix) {
447
+ const start = offset - prefix.length;
448
+ const end = offset;
449
+ const formattedItems = items.map(item => {
450
+ let label = item.name;
451
+ let insertText = item.name;
452
+ // Add quotes around JSONB keys
453
+ if (item.type === 'jsonb_key') {
454
+ insertText = `'${item.name}'`;
455
+ }
456
+ // Add table context for columns
457
+ if (item.type === 'column' && item.table) {
458
+ label = `${item.name} (${item.table})`;
459
+ }
460
+ // Add type-specific icon
461
+ let typeIcon = '📊'; // Default for columns
462
+ let sortText = item.name; // Default sort order
463
+ if (item.type === 'table') {
464
+ typeIcon = '📋';
465
+ }
466
+ else if (item.type === 'view') {
467
+ typeIcon = '👁️';
468
+ }
469
+ else if (item.type === 'jsonb_key') {
470
+ typeIcon = '🔑';
471
+ // Use 0000 prefix to sort JSONB keys to the top (numbers sort before letters)
472
+ sortText = `0000${item.name}`;
473
+ }
474
+ // Build documentation
475
+ let documentation;
476
+ if (item.type === 'column' && item.dataType && item.table) {
477
+ documentation = `${item.table}.${item.name}: ${item.dataType}`;
478
+ }
479
+ else if (item.type === 'jsonb_key' && item.keyPath) {
480
+ documentation = `JSONB key: ${item.keyPath.join(' -> ')}`;
481
+ }
482
+ return {
483
+ label: `${typeIcon} ${label}`,
484
+ insertText,
485
+ sortText,
486
+ type: item.type,
487
+ documentation
488
+ };
489
+ });
490
+ return {
491
+ start,
492
+ end,
493
+ items: formattedItems
494
+ };
495
+ }
496
+ /**
497
+ * Clear the completion cache.
498
+ */
499
+ clearCache() {
500
+ this._cache.clear();
501
+ }
502
+ }
503
+
504
+
505
+ /***/ },
506
+
507
+ /***/ "./lib/request.js"
508
+ /*!************************!*\
509
+ !*** ./lib/request.js ***!
510
+ \************************/
511
+ (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
512
+
513
+ __webpack_require__.r(__webpack_exports__);
514
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
515
+ /* harmony export */ requestAPI: () => (/* binding */ requestAPI)
516
+ /* harmony export */ });
517
+ /* harmony import */ var _jupyterlab_coreutils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @jupyterlab/coreutils */ "webpack/sharing/consume/default/@jupyterlab/coreutils");
518
+ /* harmony import */ var _jupyterlab_coreutils__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_jupyterlab_coreutils__WEBPACK_IMPORTED_MODULE_0__);
519
+ /* harmony import */ var _jupyterlab_services__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @jupyterlab/services */ "webpack/sharing/consume/default/@jupyterlab/services");
520
+ /* harmony import */ var _jupyterlab_services__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_jupyterlab_services__WEBPACK_IMPORTED_MODULE_1__);
521
+
522
+
523
+ /**
524
+ * Call the server extension
525
+ *
526
+ * @param endPoint API REST end point for the extension
527
+ * @param init Initial values for the request
528
+ * @returns The response body interpreted as JSON
529
+ */
530
+ async function requestAPI(endPoint = '', init = {}) {
531
+ // Make request to Jupyter API
532
+ const settings = _jupyterlab_services__WEBPACK_IMPORTED_MODULE_1__.ServerConnection.makeSettings();
533
+ const requestUrl = _jupyterlab_coreutils__WEBPACK_IMPORTED_MODULE_0__.URLExt.join(settings.baseUrl, 'jl-db-comp', // our server extension's API namespace
534
+ endPoint);
535
+ let response;
536
+ try {
537
+ response = await _jupyterlab_services__WEBPACK_IMPORTED_MODULE_1__.ServerConnection.makeRequest(requestUrl, init, settings);
538
+ }
539
+ catch (error) {
540
+ throw new _jupyterlab_services__WEBPACK_IMPORTED_MODULE_1__.ServerConnection.NetworkError(error);
541
+ }
542
+ let data = await response.text();
543
+ if (data.length > 0) {
544
+ try {
545
+ data = JSON.parse(data);
546
+ }
547
+ catch (error) {
548
+ console.log('Not a JSON response body.', response);
549
+ }
550
+ }
551
+ if (!response.ok) {
552
+ throw new _jupyterlab_services__WEBPACK_IMPORTED_MODULE_1__.ServerConnection.ResponseError(response, data.message || data);
553
+ }
554
+ return data;
555
+ }
556
+
557
+
558
+ /***/ }
559
+
560
+ }]);
561
+ //# sourceMappingURL=lib_index_js.a0969ed73da70f2cc451.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lib_index_js.a0969ed73da70f2cc451.js","mappings":"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","sources":["webpack://jl_db_comp/./src/api.ts","webpack://jl_db_comp/./src/index.ts","webpack://jl_db_comp/./src/provider.ts","webpack://jl_db_comp/./src/request.ts"],"sourcesContent":["import { ServerConnection } from '@jupyterlab/services';\nimport { requestAPI } from './request';\n\n/**\n * Database completion item representing a table, column, or JSONB key.\n */\nexport interface ICompletionItem {\n name: string;\n type: 'table' | 'column' | 'view' | 'jsonb_key';\n table?: string;\n dataType?: string;\n keyPath?: string[]; // For JSONB keys, the path to this key\n}\n\n/**\n * Response from the PostgreSQL completions API endpoint.\n */\nexport interface ICompletionsResponse {\n status: 'success' | 'error';\n tables: ICompletionItem[];\n columns: ICompletionItem[];\n jsonbKeys?: ICompletionItem[]; // JSONB keys from actual table data\n message?: string;\n}\n\n/**\n * Fetch PostgreSQL table and column completions from the server.\n *\n * @param dbUrl - PostgreSQL connection string (optional if using env var)\n * @param prefix - Optional prefix to filter completions\n * @param schema - Database schema name (default: 'public')\n * @param tableName - Optional table name to filter columns (only returns columns from this table)\n * @param schemaOrTable - Ambiguous identifier that could be either a schema or table name (backend will determine)\n * @param jsonbColumn - Optional JSONB column name to extract keys from\n * @param jsonbPath - Optional JSONB path for nested key extraction\n * @returns Array of completion items\n */\nexport async function fetchPostgresCompletions(\n dbUrl?: string,\n prefix = '',\n schema = 'public',\n tableName?: string,\n schemaOrTable?: string,\n jsonbColumn?: string,\n jsonbPath?: string[]\n): Promise<ICompletionItem[]> {\n try {\n const params = new URLSearchParams();\n if (dbUrl) {\n params.append('db_url', encodeURIComponent(dbUrl));\n }\n if (prefix) {\n params.append('prefix', prefix);\n }\n params.append('schema', schema);\n if (tableName) {\n params.append('table', tableName);\n }\n if (schemaOrTable) {\n params.append('schema_or_table', schemaOrTable);\n }\n if (jsonbColumn) {\n params.append('jsonb_column', jsonbColumn);\n if (jsonbPath && jsonbPath.length > 0) {\n params.append('jsonb_path', JSON.stringify(jsonbPath));\n }\n }\n\n const endpoint = `completions?${params.toString()}`;\n const response = await requestAPI<ICompletionsResponse>(endpoint, {\n method: 'GET'\n });\n\n if (response.status === 'error') {\n console.error('PostgreSQL completion error:', response.message);\n return [];\n }\n\n // If JSONB keys requested, return only those\n if (jsonbColumn && response.jsonbKeys) {\n return response.jsonbKeys;\n }\n\n // Return appropriate results based on context\n if (tableName || schemaOrTable) {\n // If we have table context, prefer columns\n return response.columns.length > 0 ? response.columns : response.tables;\n }\n\n return [...response.tables, ...response.columns];\n } catch (err) {\n if (err instanceof ServerConnection.ResponseError) {\n const status = err.response.status;\n let detail = err.message;\n\n if (\n typeof detail === 'string' &&\n (detail.includes('<!DOCTYPE') || detail.includes('<html'))\n ) {\n detail = `HTML error page (${detail.substring(0, 100)}...)`;\n }\n\n console.error(`PostgreSQL completions API failed (${status}): ${detail}`);\n } else {\n const msg = err instanceof Error ? err.message : 'Unknown error';\n console.error(`PostgreSQL completions API failed: ${msg}`);\n }\n\n return [];\n }\n}\n","import {\n JupyterFrontEnd,\n JupyterFrontEndPlugin\n} from '@jupyterlab/application';\n\nimport { ICompletionProviderManager } from '@jupyterlab/completer';\nimport { ISettingRegistry } from '@jupyterlab/settingregistry';\n\nimport { PostgresCompletionProvider } from './provider';\n\n/**\n * Plugin ID constant.\n */\nconst PLUGIN_ID = 'jl_db_comp:plugin';\n\n/**\n * Initialization data for the jl_db_comp extension.\n *\n * This plugin provides PostgreSQL table and column name completions\n * in JupyterLab notebooks and editors when typing SQL queries.\n */\nconst plugin: JupyterFrontEndPlugin<void> = {\n id: PLUGIN_ID,\n description:\n 'A JupyterLab extension to complete db queries in jupyterlab notebooks',\n autoStart: true,\n requires: [ICompletionProviderManager],\n optional: [ISettingRegistry],\n activate: (\n app: JupyterFrontEnd,\n completionManager: ICompletionProviderManager,\n settingRegistry: ISettingRegistry | null\n ) => {\n let provider: PostgresCompletionProvider;\n\n if (settingRegistry) {\n settingRegistry\n .load(PLUGIN_ID)\n .then(settings => {\n provider = new PostgresCompletionProvider(settings);\n completionManager.registerProvider(provider);\n console.log('JupyterLab extension jl_db_comp is activated!');\n })\n .catch(reason => {\n console.error('Failed to load settings for jl_db_comp:', reason);\n provider = new PostgresCompletionProvider();\n completionManager.registerProvider(provider);\n console.log('JupyterLab extension jl_db_comp is activated!');\n });\n } else {\n provider = new PostgresCompletionProvider();\n completionManager.registerProvider(provider);\n console.log('JupyterLab extension jl_db_comp is activated!');\n }\n }\n};\n\nexport default plugin;\n","import {\n CompletionHandler,\n ICompletionContext,\n ICompletionProvider\n} from '@jupyterlab/completer';\nimport { ISettingRegistry } from '@jupyterlab/settingregistry';\nimport { fetchPostgresCompletions, ICompletionItem } from './api';\n\n/**\n * Cache entry for PostgreSQL completions.\n */\ninterface ICacheEntry {\n items: ICompletionItem[];\n timestamp: number;\n}\n\n/**\n * PostgreSQL completion provider for JupyterLab.\n *\n * Provides table and column name completions from PostgreSQL databases\n * when editing SQL-like code in notebooks and editors.\n */\nexport class PostgresCompletionProvider implements ICompletionProvider {\n readonly identifier = 'jl_db_comp:postgres-completer';\n readonly renderer = null;\n\n private _cache = new Map<string, ICacheEntry>();\n private _cacheTTL = 5 * 60 * 1000; // 5 minutes in milliseconds\n private _settings: ISettingRegistry.ISettings | null = null;\n private _dbUrl = '';\n private _schema = 'public';\n private _enabled = true;\n\n /**\n * SQL keywords that trigger completion.\n */\n private readonly _sqlKeywords = [\n 'select',\n 'from',\n 'join',\n 'where',\n 'insert',\n 'update',\n 'delete',\n 'inner',\n 'left',\n 'right',\n 'outer',\n 'on',\n 'group',\n 'order',\n 'by',\n 'having',\n 'into',\n 'values',\n 'set'\n ];\n\n /**\n * Create a new PostgresCompletionProvider.\n *\n * @param settings - Optional settings registry to load database configuration\n */\n constructor(settings?: ISettingRegistry.ISettings | null) {\n if (settings) {\n this._settings = settings;\n this._loadSettings();\n\n settings.changed.connect(() => {\n this._loadSettings();\n });\n }\n }\n\n /**\n * Load database configuration from settings.\n */\n private _loadSettings(): void {\n if (!this._settings) {\n return;\n }\n\n this._dbUrl = this._settings.get('databaseUrl').composite as string;\n this._schema = this._settings.get('schema').composite as string;\n this._enabled = this._settings.get('enabled').composite as boolean;\n }\n\n /**\n * Determine if completions should be shown in the current context.\n *\n * Checks for SQL keywords or context that suggests SQL code.\n */\n async isApplicable(context: ICompletionContext): Promise<boolean> {\n if (!this._enabled) {\n return false;\n }\n\n // Get editor content from context\n const editor = context.editor;\n if (!editor) {\n return false;\n }\n\n const text = editor.model.sharedModel.getSource();\n if (!text) {\n return false;\n }\n\n const textLower = text.toLowerCase();\n\n // Check if any SQL keyword is present\n return this._sqlKeywords.some(keyword => textLower.includes(keyword));\n }\n\n /**\n * Fetch completion items for the current context.\n *\n * Uses caching to minimize database calls.\n */\n async fetch(\n request: CompletionHandler.IRequest,\n context: ICompletionContext\n ): Promise<CompletionHandler.ICompletionItemsReply> {\n if (!this._enabled) {\n return { start: request.offset, end: request.offset, items: [] };\n }\n\n const { text, offset } = request;\n\n // Extract context: schema, table, and prefix\n const extracted = this._extractContext(text, offset);\n\n // Create cache key that includes full context\n let cacheKey: string;\n if (extracted.jsonbColumn) {\n // JSONB key completion: table.column->path\n const pathStr = extracted.jsonbPath?.join('.') || '';\n const tablePrefix = extracted.schemaOrTable\n ? `${extracted.schemaOrTable}.`\n : '';\n cacheKey =\n `${tablePrefix}${extracted.jsonbColumn}->${pathStr}.${extracted.prefix}`.toLowerCase();\n } else if (extracted.schema && extracted.tableName) {\n // schema.table.prefix\n cacheKey =\n `${extracted.schema}.${extracted.tableName}.${extracted.prefix}`.toLowerCase();\n } else if (extracted.schemaOrTable) {\n // schema.prefix OR table.prefix (ambiguous)\n cacheKey = `${extracted.schemaOrTable}.${extracted.prefix}`.toLowerCase();\n } else {\n // just prefix\n cacheKey = extracted.prefix.toLowerCase();\n }\n\n // Check cache first\n const cached = this._getCached(cacheKey);\n if (cached) {\n return this._formatReply(cached, request.offset, extracted.prefix);\n }\n\n // Fetch from database\n try {\n const items = await fetchPostgresCompletions(\n this._dbUrl || undefined,\n extracted.prefix,\n extracted.schema || this._schema,\n extracted.tableName,\n extracted.schemaOrTable,\n extracted.jsonbColumn,\n extracted.jsonbPath\n );\n\n // Cache the results\n this._cache.set(cacheKey, {\n items,\n timestamp: Date.now()\n });\n\n return this._formatReply(items, request.offset, extracted.prefix);\n } catch (error) {\n console.error('Failed to fetch PostgreSQL completions:', error);\n return { start: request.offset, end: request.offset, items: [] };\n }\n }\n\n /**\n * Extract context from the text: prefix being typed, optional table name, optional schema, and JSONB context.\n *\n * Detects patterns like:\n * - \"schema.table.col\" → { schema: \"schema\", tableName: \"table\", prefix: \"col\" }\n * - \"schema.table.\" → { schema: \"schema\", tableName: \"table\", prefix: \"\" }\n * - \"schema.tab\" → { schemaOrTable: \"schema\", prefix: \"tab\" }\n * - \"schema.\" → { schemaOrTable: \"schema\", prefix: \"\" }\n * - \"table.col\" → { schemaOrTable: \"table\", prefix: \"col\" }\n * - \"table.\" → { schemaOrTable: \"table\", prefix: \"\" }\n * - \"prefix\" → { prefix: \"prefix\" }\n * - \"column_name->\" → { jsonbColumn: \"column_name\", jsonbPath: [], prefix: \"\" }\n * - \"column_name->>'key1'->\" → { jsonbColumn: \"column_name\", jsonbPath: [\"key1\"], prefix: \"\" }\n * - \"table.column_name->>'key'->\" → { schemaOrTable: \"table\", jsonbColumn: \"column_name\", jsonbPath: [\"key\"], prefix: \"\" }\n *\n * Note: For single-dot patterns (schema. or table.), the backend will determine\n * whether it's a schema (list tables) or table (list columns) by checking the database.\n */\n private _extractContext(\n text: string,\n offset: number\n ): {\n prefix: string;\n tableName?: string;\n schema?: string;\n schemaOrTable?: string;\n jsonbColumn?: string;\n jsonbPath?: string[];\n } {\n const beforeCursor = text.substring(0, offset);\n\n // JSONB pattern: Detect -> or ->> operators\n // Examples: metadata-> or content -> or patients.metadata->>'key'->\n if (beforeCursor.includes('->')) {\n // Much simpler approach: find the last -> or ->> and work backwards\n // Look for: word characters, optional dot+word, then ->, then anything\n // Pattern: (word.)?word -> rest\n const simpleMatch = beforeCursor.match(/([\\w]+\\.)?([\\w]+)\\s*->\\s*(.*)$/);\n\n if (simpleMatch) {\n const tableOrSchema = simpleMatch[1]\n ? simpleMatch[1].slice(0, -1)\n : undefined; // Remove trailing dot\n const columnName = simpleMatch[2];\n const afterOperator = simpleMatch[3];\n\n // Parse the path after the first ->\n // Example: \"'key1'->>'key2'->\" or \"key1\" or \"\"\n const jsonbPath: string[] = [];\n const pathRegex = /['\"]?([\\w]+)['\"]?\\s*->/g;\n let pathMatch;\n while ((pathMatch = pathRegex.exec(afterOperator)) !== null) {\n jsonbPath.push(pathMatch[1]);\n }\n\n // Get the current prefix (what's being typed after the last ->)\n // Remove any keys that are part of the path\n const lastArrowIndex = afterOperator.lastIndexOf('->');\n let currentPrefix = '';\n if (lastArrowIndex >= 0) {\n currentPrefix = afterOperator\n .substring(lastArrowIndex + 2)\n .trim()\n .replace(/['\"]/g, '');\n } else {\n // No nested path, just get whatever is after the ->\n currentPrefix = afterOperator.trim().replace(/['\"]/g, '');\n }\n\n return {\n schemaOrTable: tableOrSchema,\n jsonbColumn: columnName,\n jsonbPath,\n prefix: currentPrefix\n };\n }\n }\n\n // Three-part pattern: schema.table.column\n const threePartMatch = beforeCursor.match(/([\\w]+)\\.([\\w]+)\\.([\\w]*)$/);\n if (threePartMatch) {\n return {\n schema: threePartMatch[1],\n tableName: threePartMatch[2],\n prefix: threePartMatch[3]\n };\n }\n\n // Two-part pattern: could be schema.table OR table.column\n // Backend will determine which by checking if first part is a schema\n const twoPartMatch = beforeCursor.match(/([\\w]+)\\.([\\w]*)$/);\n if (twoPartMatch) {\n return {\n schemaOrTable: twoPartMatch[1],\n prefix: twoPartMatch[2]\n };\n }\n\n // Single word: could be a table name OR a column name\n // Check if there's a FROM clause in the query to determine context\n const wordMatch = beforeCursor.match(/[\\w]+$/);\n const prefix = wordMatch ? wordMatch[0] : '';\n\n // Look for FROM clause in the entire text (before or after cursor)\n // Match patterns like: FROM table, FROM schema.table, FROM table AS alias\n const fullText = text.toLowerCase();\n const fromMatch = fullText.match(/\\bfrom\\s+([\\w]+\\.)?[\\w]+/);\n\n if (fromMatch) {\n // Extract the table name (with optional schema)\n const fromClause = fromMatch[0];\n const tableMatch = fromClause.match(/\\bfrom\\s+(?:([\\w]+)\\.)?([\\w]+)/);\n\n if (tableMatch) {\n const schema = tableMatch[1];\n const table = tableMatch[2];\n\n // If we have a schema, return schema.table pattern\n if (schema) {\n return {\n schema,\n tableName: table,\n prefix\n };\n }\n\n // Otherwise, return table as schemaOrTable (backend will check if it's a table)\n return {\n schemaOrTable: table,\n prefix\n };\n }\n }\n\n // No FROM clause found, just return prefix (will suggest tables)\n return {\n prefix\n };\n }\n\n /**\n * Get cached completion items if still valid.\n */\n private _getCached(prefix: string): ICompletionItem[] | null {\n const key = prefix.toLowerCase();\n const entry = this._cache.get(key);\n\n if (!entry) {\n return null;\n }\n\n const age = Date.now() - entry.timestamp;\n if (age > this._cacheTTL) {\n this._cache.delete(key);\n return null;\n }\n\n return entry.items;\n }\n\n /**\n * Format completion items into the reply format expected by JupyterLab.\n */\n private _formatReply(\n items: ICompletionItem[],\n offset: number,\n prefix: string\n ): CompletionHandler.ICompletionItemsReply {\n const start = offset - prefix.length;\n const end = offset;\n\n const formattedItems = items.map(item => {\n let label = item.name;\n let insertText = item.name;\n\n // Add quotes around JSONB keys\n if (item.type === 'jsonb_key') {\n insertText = `'${item.name}'`;\n }\n\n // Add table context for columns\n if (item.type === 'column' && item.table) {\n label = `${item.name} (${item.table})`;\n }\n\n // Add type-specific icon\n let typeIcon = '📊'; // Default for columns\n let sortText = item.name; // Default sort order\n\n if (item.type === 'table') {\n typeIcon = '📋';\n } else if (item.type === 'view') {\n typeIcon = '👁️';\n } else if (item.type === 'jsonb_key') {\n typeIcon = '🔑';\n // Use 0000 prefix to sort JSONB keys to the top (numbers sort before letters)\n sortText = `0000${item.name}`;\n }\n\n // Build documentation\n let documentation: string | undefined;\n if (item.type === 'column' && item.dataType && item.table) {\n documentation = `${item.table}.${item.name}: ${item.dataType}`;\n } else if (item.type === 'jsonb_key' && item.keyPath) {\n documentation = `JSONB key: ${item.keyPath.join(' -> ')}`;\n }\n\n return {\n label: `${typeIcon} ${label}`,\n insertText,\n sortText,\n type: item.type,\n documentation\n };\n });\n\n return {\n start,\n end,\n items: formattedItems\n };\n }\n\n /**\n * Clear the completion cache.\n */\n clearCache(): void {\n this._cache.clear();\n }\n}\n","import { URLExt } from '@jupyterlab/coreutils';\n\nimport { ServerConnection } from '@jupyterlab/services';\n\n/**\n * Call the server extension\n *\n * @param endPoint API REST end point for the extension\n * @param init Initial values for the request\n * @returns The response body interpreted as JSON\n */\nexport async function requestAPI<T>(\n endPoint = '',\n init: RequestInit = {}\n): Promise<T> {\n // Make request to Jupyter API\n const settings = ServerConnection.makeSettings();\n const requestUrl = URLExt.join(\n settings.baseUrl,\n 'jl-db-comp', // our server extension's API namespace\n endPoint\n );\n\n let response: Response;\n try {\n response = await ServerConnection.makeRequest(requestUrl, init, settings);\n } catch (error) {\n throw new ServerConnection.NetworkError(error as any);\n }\n\n let data: any = await response.text();\n\n if (data.length > 0) {\n try {\n data = JSON.parse(data);\n } catch (error) {\n console.log('Not a JSON response body.', response);\n }\n }\n\n if (!response.ok) {\n throw new ServerConnection.ResponseError(response, data.message || data);\n }\n\n return data;\n}\n"],"names":[],"ignoreList":[],"sourceRoot":""}