@health-samurai/react-components 0.0.0-alpha.18 → 0.0.0-alpha.20

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 (74) hide show
  1. package/LICENSE +21 -0
  2. package/dist/bundle.css +51 -33
  3. package/dist/src/components/code-editor/fhir-autocomplete.d.ts +70 -0
  4. package/dist/src/components/code-editor/fhir-autocomplete.d.ts.map +1 -0
  5. package/dist/src/components/code-editor/fhir-autocomplete.js +1849 -0
  6. package/dist/src/components/code-editor/fhir-autocomplete.js.map +1 -0
  7. package/dist/src/components/code-editor/fhir-autocomplete.test.js +1099 -0
  8. package/dist/src/components/code-editor/fhir-autocomplete.test.js.map +1 -0
  9. package/dist/src/components/code-editor/http/index.d.ts +9 -1
  10. package/dist/src/components/code-editor/http/index.d.ts.map +1 -1
  11. package/dist/src/components/code-editor/http/index.js +423 -3
  12. package/dist/src/components/code-editor/http/index.js.map +1 -1
  13. package/dist/src/components/code-editor/index.d.ts +13 -4
  14. package/dist/src/components/code-editor/index.d.ts.map +1 -1
  15. package/dist/src/components/code-editor/index.js +505 -96
  16. package/dist/src/components/code-editor/index.js.map +1 -1
  17. package/dist/src/components/code-editor/json-ast.d.ts +46 -0
  18. package/dist/src/components/code-editor/json-ast.d.ts.map +1 -0
  19. package/dist/src/components/code-editor/json-ast.js +465 -0
  20. package/dist/src/components/code-editor/json-ast.js.map +1 -0
  21. package/dist/src/components/code-editor/json-ast.test.js +206 -0
  22. package/dist/src/components/code-editor/json-ast.test.js.map +1 -0
  23. package/dist/src/components/code-editor/sql-completion.d.ts +22 -0
  24. package/dist/src/components/code-editor/sql-completion.d.ts.map +1 -0
  25. package/dist/src/components/code-editor/sql-completion.js +895 -0
  26. package/dist/src/components/code-editor/sql-completion.js.map +1 -0
  27. package/dist/src/components/date-picker-input.d.ts +10 -0
  28. package/dist/src/components/date-picker-input.d.ts.map +1 -0
  29. package/dist/src/components/date-picker-input.js +90 -0
  30. package/dist/src/components/date-picker-input.js.map +1 -0
  31. package/dist/src/components/date-picker-input.stories.js +76 -0
  32. package/dist/src/components/date-picker-input.stories.js.map +1 -0
  33. package/dist/src/index.d.ts +1 -0
  34. package/dist/src/index.d.ts.map +1 -1
  35. package/dist/src/index.js +1 -0
  36. package/dist/src/index.js.map +1 -1
  37. package/dist/src/shadcn/components/ui/alert-dialog.d.ts +1 -1
  38. package/dist/src/shadcn/components/ui/calendar.d.ts +1 -1
  39. package/dist/src/shadcn/components/ui/carousel.d.ts +1 -1
  40. package/dist/src/shadcn/components/ui/chart.d.ts +3 -3
  41. package/dist/src/shadcn/components/ui/chart.d.ts.map +1 -1
  42. package/dist/src/shadcn/components/ui/chart.js +1 -1
  43. package/dist/src/shadcn/components/ui/chart.js.map +1 -1
  44. package/dist/src/shadcn/components/ui/command.d.ts +1 -1
  45. package/dist/src/shadcn/components/ui/pagination.d.ts +1 -1
  46. package/dist/src/shadcn/components/ui/resizable.stories.js +2 -2
  47. package/dist/src/shadcn/components/ui/resizable.stories.js.map +1 -1
  48. package/dist/src/shadcn/components/ui/sidebar.d.ts +4 -4
  49. package/dist/src/shadcn/components/ui/tabs.d.ts +3 -1
  50. package/dist/src/shadcn/components/ui/tabs.d.ts.map +1 -1
  51. package/dist/src/shadcn/components/ui/tabs.js +129 -2
  52. package/dist/src/shadcn/components/ui/tabs.js.map +1 -1
  53. package/dist/src/shadcn/components/ui/tabs.stories.js +1 -1
  54. package/dist/src/shadcn/components/ui/tabs.stories.js.map +1 -1
  55. package/dist/src/shadcn/components/ui/toggle-group.d.ts +1 -1
  56. package/dist/src/typography.css +1 -1
  57. package/package.json +24 -19
  58. package/src/components/code-editor/fhir-autocomplete.test.ts +993 -0
  59. package/src/components/code-editor/fhir-autocomplete.ts +2321 -0
  60. package/src/components/code-editor/http/index.ts +339 -2
  61. package/src/components/code-editor/index.tsx +593 -102
  62. package/src/components/code-editor/json-ast.test.ts +230 -0
  63. package/src/components/code-editor/json-ast.ts +590 -0
  64. package/src/components/code-editor/sql-completion.ts +1105 -0
  65. package/src/components/date-picker-input.stories.tsx +79 -0
  66. package/src/components/date-picker-input.tsx +104 -0
  67. package/src/index.tsx +1 -0
  68. package/src/shadcn/components/ui/chart.tsx +6 -3
  69. package/src/shadcn/components/ui/resizable.stories.tsx +2 -2
  70. package/src/shadcn/components/ui/tabs.stories.tsx +1 -1
  71. package/src/shadcn/components/ui/tabs.tsx +160 -2
  72. package/src/typography.css +1 -1
  73. package/dist/src/components/code-editor/http/grammar/http.test.d.ts +0 -2
  74. package/dist/src/components/code-editor/http/grammar/http.test.d.ts.map +0 -1
@@ -0,0 +1,895 @@
1
+ import { autocompletion, startCompletion } from "@codemirror/autocomplete";
2
+ import { EditorState } from "@codemirror/state";
3
+ import { EditorView } from "@codemirror/view";
4
+ // ── SQL queries ──
5
+ const TABLES_QUERY = `SELECT table_schema, table_name FROM information_schema.tables WHERE table_schema NOT IN ('pg_catalog', 'information_schema', 'pgagent') AND table_type = 'BASE TABLE' ORDER BY table_schema, table_name`;
6
+ const JSONB_COLUMNS_QUERY = `SELECT c.table_schema, c.table_name, c.column_name FROM information_schema.columns c JOIN information_schema.tables t ON c.table_schema = t.table_schema AND c.table_name = t.table_name WHERE t.table_type = 'BASE TABLE' AND c.table_schema NOT IN ('pg_catalog', 'information_schema', 'pgagent') AND c.udt_name = 'jsonb'`;
7
+ const FUNCTIONS_QUERY = `SELECT DISTINCT p.proname AS name FROM pg_proc p JOIN pg_namespace n ON p.pronamespace = n.oid LEFT JOIN pg_depend d ON d.objid = p.oid AND d.deptype = 'e' WHERE n.nspname NOT IN ('pg_catalog', 'information_schema') AND d.objid IS NULL ORDER BY p.proname`;
8
+ const COLUMNS_QUERY = `SELECT c.table_schema, c.table_name, c.column_name, c.data_type FROM information_schema.columns c JOIN information_schema.tables t ON c.table_schema = t.table_schema AND c.table_name = t.table_name WHERE t.table_type = 'BASE TABLE' AND c.table_schema NOT IN ('pg_catalog', 'information_schema', 'pgagent') ORDER BY c.table_schema, c.table_name, c.ordinal_position`;
9
+ // ── FHIR StructureDefinition processing ──
10
+ function isExpandedVariant(path, unionBases) {
11
+ const parts = path.split(".");
12
+ if (parts.length < 2) return false;
13
+ const parentPath = parts.slice(0, -1).join(".");
14
+ const name = parts[parts.length - 1];
15
+ for (const base of unionBases){
16
+ const baseParts = base.split(".");
17
+ const baseName = baseParts[baseParts.length - 1];
18
+ const baseParent = baseParts.slice(0, -1).join(".");
19
+ if (parentPath === baseParent && name.startsWith(baseName) && name.length > baseName.length && /^[A-Z]/.test(name.slice(baseName.length))) {
20
+ return true;
21
+ }
22
+ }
23
+ return false;
24
+ }
25
+ function buildFromStructureDefinition(sd) {
26
+ const elements = sd.snapshot?.element ?? [];
27
+ const result = {};
28
+ const unionBases = new Set();
29
+ for (const el of elements){
30
+ if (!el.path) continue;
31
+ const name = el.path.split(".").pop() ?? "";
32
+ if (name.endsWith("[x]")) {
33
+ unionBases.add(el.path.replace(/\[x\]$/, ""));
34
+ }
35
+ }
36
+ for (const el of elements){
37
+ if (!el.path) continue;
38
+ const parts = el.path.split(".");
39
+ if (parts.length < 2) continue;
40
+ if (isExpandedVariant(el.path, unionBases)) continue;
41
+ const parentPath = parts.slice(0, -1).join(".");
42
+ const rawName = parts[parts.length - 1];
43
+ if (!result[parentPath]) result[parentPath] = [];
44
+ if (rawName.endsWith("[x]")) {
45
+ const name = rawName.slice(0, -3);
46
+ if (result[parentPath].some((f)=>f.name === name)) continue;
47
+ result[parentPath].push({
48
+ name,
49
+ datatype: "union",
50
+ isArray: el.max === "*",
51
+ description: el.short ?? el.definition
52
+ });
53
+ const unionPath = `${parentPath}.${name}`;
54
+ if (!result[unionPath]) result[unionPath] = [];
55
+ for (const t of el.type ?? []){
56
+ if (!result[unionPath].some((f)=>f.name === t.code)) {
57
+ result[unionPath].push({
58
+ name: t.code,
59
+ datatype: t.code,
60
+ isArray: false,
61
+ description: el.short ?? el.definition
62
+ });
63
+ }
64
+ }
65
+ } else {
66
+ if (result[parentPath].some((f)=>f.name === rawName)) continue;
67
+ result[parentPath].push({
68
+ name: rawName,
69
+ datatype: el.type?.[0]?.code ?? "",
70
+ isArray: el.max === "*",
71
+ description: el.short ?? el.definition
72
+ });
73
+ }
74
+ }
75
+ return result;
76
+ }
77
+ function transformReferenceFields(result) {
78
+ for (const [path, children] of Object.entries(result)){
79
+ const hasReferenceField = children.some((c)=>c.name === "reference");
80
+ if (!hasReferenceField) continue;
81
+ result[path] = children.flatMap((child)=>{
82
+ if (child.name === "reference") {
83
+ return [
84
+ {
85
+ name: "id",
86
+ datatype: "string",
87
+ isArray: false,
88
+ description: "Resource ID"
89
+ },
90
+ {
91
+ name: "resourceType",
92
+ datatype: "string",
93
+ isArray: false,
94
+ description: "Resource type"
95
+ }
96
+ ];
97
+ }
98
+ return [
99
+ child
100
+ ];
101
+ });
102
+ }
103
+ }
104
+ // ── SQL parsing utilities ──
105
+ const SQL_TABLE_KEYWORDS = /\b(?:from|join|inner\s+join|left\s+join|right\s+join|full\s+join|cross\s+join|into|update|table)\s+$/i;
106
+ function isInsideString(textBefore) {
107
+ let count = 0;
108
+ for(let i = 0; i < textBefore.length; i++){
109
+ if (textBefore[i] === "'") {
110
+ if (i + 1 < textBefore.length && textBefore[i + 1] === "'") {
111
+ i++;
112
+ } else {
113
+ count++;
114
+ }
115
+ }
116
+ }
117
+ return count % 2 !== 0;
118
+ }
119
+ function isInJsonbContext(textBefore) {
120
+ return parseJsonbChain(textBefore) !== null;
121
+ }
122
+ function parseJsonbChain(textBefore) {
123
+ const pathOpMatch = textBefore.match(/((?:\w+\.)?\w+)\s*#>>?\s*'\{([^}]*)$/);
124
+ if (pathOpMatch) {
125
+ const ref = pathOpMatch[1];
126
+ const pathContent = pathOpMatch[2];
127
+ const segments = pathContent ? pathContent.split(",") : [];
128
+ const partialInput = segments.length > 0 ? segments.pop() ?? "" : "";
129
+ const lastRawSegment = segments.length > 0 ? segments[segments.length - 1] ?? null : null;
130
+ const path = segments.filter((s)=>!/^\d+$/.test(s));
131
+ const dotParts = ref.split(".");
132
+ if (dotParts.length === 2) {
133
+ return {
134
+ tableOrAlias: dotParts[0],
135
+ column: dotParts[1],
136
+ path,
137
+ isPathOp: true,
138
+ partialInput,
139
+ insideQuote: false,
140
+ lastRawSegment
141
+ };
142
+ }
143
+ return {
144
+ tableOrAlias: null,
145
+ column: dotParts[0],
146
+ path,
147
+ isPathOp: true,
148
+ partialInput,
149
+ insideQuote: false,
150
+ lastRawSegment
151
+ };
152
+ }
153
+ const arrowPattern = /(?:((?:\w+\.)?\w+)((?:\s*->>?\s*(?:'[^']*'|\d+))*)\s*->>?\s*)('?)([^']*)?$/;
154
+ const arrowMatch = textBefore.match(arrowPattern);
155
+ if (!arrowMatch) return null;
156
+ const ref = arrowMatch[1];
157
+ const chainPart = arrowMatch[2] || "";
158
+ const insideQuote = arrowMatch[3] === "'";
159
+ const partialInput = arrowMatch[4] ?? "";
160
+ const chainSegments = [];
161
+ let lastRawSeg = null;
162
+ const segmentRegex = /->>?\s*(?:'([^']*)'|(\d+))/g;
163
+ for(let m = segmentRegex.exec(chainPart); m !== null; m = segmentRegex.exec(chainPart)){
164
+ const seg = m[1] ?? m[2] ?? "";
165
+ lastRawSeg = seg;
166
+ if (!/^\d+$/.test(seg)) {
167
+ chainSegments.push(seg);
168
+ }
169
+ }
170
+ const dotParts = ref.split(".");
171
+ if (dotParts.length === 2) {
172
+ return {
173
+ tableOrAlias: dotParts[0],
174
+ column: dotParts[1],
175
+ path: chainSegments,
176
+ isPathOp: false,
177
+ partialInput,
178
+ insideQuote,
179
+ lastRawSegment: lastRawSeg
180
+ };
181
+ }
182
+ return {
183
+ tableOrAlias: null,
184
+ column: dotParts[0],
185
+ path: chainSegments,
186
+ isPathOp: false,
187
+ partialInput,
188
+ insideQuote,
189
+ lastRawSegment: lastRawSeg
190
+ };
191
+ }
192
+ function buildAliasMap(sql, schemas) {
193
+ const aliases = {};
194
+ const regex = /\b(?:FROM|JOIN)\s+((?:\w+\.)?\w+)(?:\s+(?:AS\s+)?(\w+))?/gi;
195
+ for(let match = regex.exec(sql); match !== null; match = regex.exec(sql)){
196
+ const fullTable = match[1];
197
+ const alias = match[2];
198
+ let schema;
199
+ let table;
200
+ if (fullTable.includes(".")) {
201
+ const parts = fullTable.split(".");
202
+ schema = parts[0];
203
+ table = parts[1];
204
+ } else {
205
+ table = fullTable;
206
+ let found = null;
207
+ for (const [s, tables] of Object.entries(schemas)){
208
+ if (tables.includes(table)) {
209
+ found = s;
210
+ if (s === "public") break;
211
+ }
212
+ }
213
+ schema = found ?? "public";
214
+ }
215
+ if (alias) {
216
+ aliases[alias.toLowerCase()] = {
217
+ schema,
218
+ table
219
+ };
220
+ }
221
+ aliases[table.toLowerCase()] = {
222
+ schema,
223
+ table
224
+ };
225
+ }
226
+ return aliases;
227
+ }
228
+ function tableToResourceType(table) {
229
+ return table.split("_").map((s)=>s.charAt(0).toUpperCase() + s.slice(1)).join("");
230
+ }
231
+ function getCurrentStatement(doc, pos) {
232
+ let start = 0;
233
+ let end = doc.length;
234
+ const before = doc.lastIndexOf(";", pos - 1);
235
+ if (before !== -1) start = before + 1;
236
+ const after = doc.indexOf(";", pos);
237
+ if (after !== -1) end = after;
238
+ return doc.slice(start, end);
239
+ }
240
+ // ── Completion result builders ──
241
+ function buildJsonbResult(chain, pathChildren, resourceType, context) {
242
+ const lookupPath = chain.path.length > 0 ? `${resourceType}.${chain.path.join(".")}` : resourceType;
243
+ const children = pathChildren[lookupPath];
244
+ if (!children || children.length === 0) return null;
245
+ const partial = chain.partialInput.toLowerCase();
246
+ const filtered = partial ? children.filter((f)=>f.name.toLowerCase().startsWith(partial)) : children;
247
+ if (filtered.length === 0) return null;
248
+ if (chain.isPathOp) {
249
+ return {
250
+ from: context.pos - chain.partialInput.length,
251
+ validFor: /^\w*$/,
252
+ options: filtered.map((f)=>({
253
+ label: f.name,
254
+ type: "property",
255
+ detail: f.datatype + (f.isArray ? "[]" : ""),
256
+ ...f.description != null ? {
257
+ info: f.description
258
+ } : {}
259
+ }))
260
+ };
261
+ }
262
+ if (chain.insideQuote) {
263
+ return {
264
+ from: context.pos - chain.partialInput.length,
265
+ validFor: /^\w*$/,
266
+ options: filtered.map((f)=>({
267
+ label: f.name,
268
+ type: "property",
269
+ detail: f.datatype + (f.isArray ? "[]" : ""),
270
+ ...f.description != null ? {
271
+ info: f.description
272
+ } : {},
273
+ apply: (view, _completion, from, to)=>{
274
+ const after = view.state.sliceDoc(to, to + 1);
275
+ const end = after === "'" ? to + 1 : to;
276
+ const insert = `${f.name}'`;
277
+ view.dispatch({
278
+ changes: {
279
+ from,
280
+ to: end,
281
+ insert
282
+ },
283
+ selection: {
284
+ anchor: from + insert.length
285
+ }
286
+ });
287
+ }
288
+ }))
289
+ };
290
+ }
291
+ return {
292
+ from: context.pos - chain.partialInput.length,
293
+ validFor: /^'?\w*'?$/,
294
+ options: filtered.map((f)=>({
295
+ label: `'${f.name}'`,
296
+ type: "property",
297
+ detail: f.datatype + (f.isArray ? "[]" : ""),
298
+ ...f.description != null ? {
299
+ info: f.description
300
+ } : {},
301
+ apply: `'${f.name}'`
302
+ }))
303
+ };
304
+ }
305
+ function isArrayPosition(chain, pathChildren, resourceType) {
306
+ if (!chain.lastRawSegment || /^\d+$/.test(chain.lastRawSegment)) return false;
307
+ if (!chain.isPathOp && chain.insideQuote) return false;
308
+ const fieldName = chain.lastRawSegment;
309
+ const parentPath = chain.path.length > 1 ? `${resourceType}.${chain.path.slice(0, -1).join(".")}` : resourceType;
310
+ const parentChildren = pathChildren[parentPath];
311
+ if (!parentChildren) return false;
312
+ const element = parentChildren.find((f)=>f.name === fieldName);
313
+ return !!element?.isArray;
314
+ }
315
+ function buildArrayIndexResult(chain, context) {
316
+ return {
317
+ from: context.pos - chain.partialInput.length,
318
+ options: [
319
+ {
320
+ label: "0",
321
+ type: "enum",
322
+ detail: "array index"
323
+ }
324
+ ]
325
+ };
326
+ }
327
+ async function resolveNestedTypes(pathChildren, resourceType, path, fetchSchema) {
328
+ for(let i = 0; i < path.length; i++){
329
+ const currentPath = `${resourceType}.${path.slice(0, i + 1).join(".")}`;
330
+ if (pathChildren[currentPath]) continue;
331
+ const parentPath = i === 0 ? resourceType : `${resourceType}.${path.slice(0, i).join(".")}`;
332
+ const parentChildren = pathChildren[parentPath];
333
+ if (!parentChildren) return;
334
+ const segmentName = path[i];
335
+ const element = parentChildren.find((f)=>f.name === segmentName);
336
+ if (!element?.datatype) return;
337
+ if (element.datatype === "union") continue;
338
+ const firstChar = element.datatype[0];
339
+ if (firstChar !== firstChar.toUpperCase()) return;
340
+ const typeChildren = await fetchSchema(element.datatype);
341
+ if (!typeChildren) return;
342
+ const typeName = element.datatype;
343
+ for (const [key, children] of Object.entries(typeChildren)){
344
+ const suffix = key === typeName ? "" : key.slice(typeName.length);
345
+ pathChildren[currentPath + suffix] = children;
346
+ }
347
+ }
348
+ }
349
+ // ── Completion extensions ──
350
+ function tableCompletionExtension(schemas) {
351
+ const source = (context)=>{
352
+ const line = context.state.doc.lineAt(context.pos);
353
+ const textBefore = line.text.slice(0, context.pos - line.from);
354
+ if (isInsideString(textBefore)) return null;
355
+ const schemaDot = textBefore.match(/(\w+)\.(\w*)$/);
356
+ if (schemaDot) {
357
+ const schemaName = schemaDot[1];
358
+ const tables = schemas[schemaName];
359
+ if (!tables) return null;
360
+ return {
361
+ from: context.pos - (schemaDot[2] ?? "").length,
362
+ options: tables.map((t)=>({
363
+ label: t,
364
+ type: "table"
365
+ }))
366
+ };
367
+ }
368
+ const word = context.matchBefore(/\w*/);
369
+ if (!word) return null;
370
+ const beforeWord = textBefore.slice(0, word.from - line.from);
371
+ if (!SQL_TABLE_KEYWORDS.test(beforeWord) && !context.explicit) return null;
372
+ const options = [];
373
+ for (const [schema, tables] of Object.entries(schemas)){
374
+ options.push({
375
+ label: `${schema}.`,
376
+ type: "keyword",
377
+ detail: "schema"
378
+ });
379
+ for (const table of tables){
380
+ if (schema === "public") {
381
+ options.push({
382
+ label: table,
383
+ type: "table"
384
+ });
385
+ } else {
386
+ options.push({
387
+ label: `${schema}.${table}`,
388
+ type: "table",
389
+ detail: schema
390
+ });
391
+ }
392
+ }
393
+ }
394
+ return {
395
+ from: word.from,
396
+ options
397
+ };
398
+ };
399
+ return EditorState.languageData.of(()=>[
400
+ {
401
+ autocomplete: source
402
+ }
403
+ ]);
404
+ }
405
+ function columnCompletionExtension(ctx) {
406
+ const source = (context)=>{
407
+ const line = context.state.doc.lineAt(context.pos);
408
+ const textBefore = line.text.slice(0, context.pos - line.from);
409
+ if (isInsideString(textBefore)) return null;
410
+ const fullDoc = context.state.doc.toString();
411
+ const statement = getCurrentStatement(fullDoc, context.pos);
412
+ const aliases = buildAliasMap(statement, ctx.schemas);
413
+ if (Object.keys(aliases).length === 0) return null;
414
+ const aliasDot = textBefore.match(/(\w+)\.(\w*)$/);
415
+ if (aliasDot) {
416
+ const beforeAlias = textBefore.slice(0, textBefore.length - aliasDot[0].length);
417
+ if (SQL_TABLE_KEYWORDS.test(beforeAlias)) return null;
418
+ const aliasName = aliasDot[1];
419
+ const entry = aliases[aliasName.toLowerCase()];
420
+ if (!entry) return null;
421
+ const key = `${entry.schema}.${entry.table}`;
422
+ const cols = ctx.columns[key];
423
+ if (!cols || cols.length === 0) return null;
424
+ return {
425
+ from: context.pos - (aliasDot[2] ?? "").length,
426
+ options: cols.map((c)=>({
427
+ label: c.name,
428
+ type: "variable",
429
+ detail: c.dataType
430
+ }))
431
+ };
432
+ }
433
+ const word = context.matchBefore(/\w*/);
434
+ if (!word) return null;
435
+ if (word.from === word.to && !context.explicit) return null;
436
+ const textBeforeWord = textBefore.slice(0, word.from - line.from);
437
+ if (SQL_TABLE_KEYWORDS.test(textBeforeWord)) return null;
438
+ const seen = new Set();
439
+ const options = [];
440
+ for (const entry of Object.values(aliases)){
441
+ const key = `${entry.schema}.${entry.table}`;
442
+ const cols = ctx.columns[key];
443
+ if (!cols) continue;
444
+ for (const c of cols){
445
+ const dedup = `${c.name}::${entry.table}`;
446
+ if (seen.has(dedup)) continue;
447
+ seen.add(dedup);
448
+ options.push({
449
+ label: c.name,
450
+ type: "variable",
451
+ detail: `${c.dataType} · ${entry.table}`
452
+ });
453
+ }
454
+ }
455
+ if (options.length === 0) return null;
456
+ return {
457
+ from: word.from,
458
+ options
459
+ };
460
+ };
461
+ return EditorState.languageData.of(()=>[
462
+ {
463
+ autocomplete: source
464
+ }
465
+ ]);
466
+ }
467
+ function triggerCompletionAfter(view) {
468
+ requestAnimationFrame(()=>startCompletion(view));
469
+ }
470
+ function jsonbOperatorExtension() {
471
+ const source = (context)=>{
472
+ const line = context.state.doc.lineAt(context.pos);
473
+ const textBefore = line.text.slice(0, context.pos - line.from);
474
+ if (/\w\s*#$/.test(textBefore)) {
475
+ return {
476
+ from: context.pos - 1,
477
+ options: [
478
+ {
479
+ label: "#>> '{}'",
480
+ type: "operator",
481
+ apply: (view, _completion, from, to)=>{
482
+ const insert = "#>> '{";
483
+ view.dispatch({
484
+ changes: {
485
+ from,
486
+ to,
487
+ insert: `${insert}}' `
488
+ },
489
+ selection: {
490
+ anchor: from + insert.length
491
+ }
492
+ });
493
+ triggerCompletionAfter(view);
494
+ }
495
+ },
496
+ {
497
+ label: "#> '{}'",
498
+ type: "operator",
499
+ apply: (view, _completion, from, to)=>{
500
+ const insert = "#> '{";
501
+ view.dispatch({
502
+ changes: {
503
+ from,
504
+ to,
505
+ insert: `${insert}}' `
506
+ },
507
+ selection: {
508
+ anchor: from + insert.length
509
+ }
510
+ });
511
+ triggerCompletionAfter(view);
512
+ }
513
+ }
514
+ ]
515
+ };
516
+ }
517
+ if (/\w\s*->$/.test(textBefore) && !/\w\s*->>$/.test(textBefore)) {
518
+ return {
519
+ from: context.pos - 2,
520
+ options: [
521
+ {
522
+ label: "->> ''",
523
+ type: "operator",
524
+ apply: (view, _completion, from, to)=>{
525
+ const insert = "->> '";
526
+ view.dispatch({
527
+ changes: {
528
+ from,
529
+ to,
530
+ insert: `${insert}' `
531
+ },
532
+ selection: {
533
+ anchor: from + insert.length
534
+ }
535
+ });
536
+ triggerCompletionAfter(view);
537
+ }
538
+ },
539
+ {
540
+ label: "-> ''",
541
+ type: "operator",
542
+ apply: (view, _completion, from, to)=>{
543
+ const insert = "-> '";
544
+ view.dispatch({
545
+ changes: {
546
+ from,
547
+ to,
548
+ insert: `${insert}' `
549
+ },
550
+ selection: {
551
+ anchor: from + insert.length
552
+ }
553
+ });
554
+ triggerCompletionAfter(view);
555
+ }
556
+ },
557
+ {
558
+ label: "->> 0",
559
+ type: "operator",
560
+ apply: (view, _completion, from, to)=>{
561
+ const insert = "->> ";
562
+ view.dispatch({
563
+ changes: {
564
+ from,
565
+ to,
566
+ insert: `${insert}0 `
567
+ },
568
+ selection: {
569
+ anchor: from + insert.length,
570
+ head: from + insert.length + 1
571
+ }
572
+ });
573
+ }
574
+ },
575
+ {
576
+ label: "-> 0",
577
+ type: "operator",
578
+ apply: (view, _completion, from, to)=>{
579
+ const insert = "-> ";
580
+ view.dispatch({
581
+ changes: {
582
+ from,
583
+ to,
584
+ insert: `${insert}0 `
585
+ },
586
+ selection: {
587
+ anchor: from + insert.length,
588
+ head: from + insert.length + 1
589
+ }
590
+ });
591
+ }
592
+ }
593
+ ]
594
+ };
595
+ }
596
+ return null;
597
+ };
598
+ return EditorState.languageData.of(()=>[
599
+ {
600
+ autocomplete: source
601
+ }
602
+ ]);
603
+ }
604
+ function jsonbCompletionExtension(ctx) {
605
+ const resolveChain = (context)=>{
606
+ const line = context.state.doc.lineAt(context.pos);
607
+ const textBefore = line.text.slice(0, context.pos - line.from);
608
+ const chain = parseJsonbChain(textBefore);
609
+ if (!chain) return null;
610
+ const fullDoc = context.state.doc.toString();
611
+ const statement = getCurrentStatement(fullDoc, context.pos);
612
+ const aliases = buildAliasMap(statement, ctx.schemas);
613
+ let resolved = null;
614
+ if (chain.tableOrAlias) {
615
+ resolved = aliases[chain.tableOrAlias.toLowerCase()] ?? null;
616
+ } else {
617
+ for (const entry of Object.values(aliases)){
618
+ const key = `${entry.schema}.${entry.table}`;
619
+ const cols = ctx.jsonbColumns[key];
620
+ if (cols?.includes(chain.column)) {
621
+ resolved = entry;
622
+ break;
623
+ }
624
+ }
625
+ }
626
+ if (!resolved) return null;
627
+ const jsonbKey = `${resolved.schema}.${resolved.table}`;
628
+ const jsonbCols = ctx.jsonbColumns[jsonbKey];
629
+ if (!jsonbCols?.includes(chain.column)) return null;
630
+ if (chain.column !== "resource") return null;
631
+ const resourceType = tableToResourceType(resolved.table);
632
+ if (ctx.sdNotFound.has(resourceType)) return null;
633
+ return {
634
+ chain,
635
+ resourceType
636
+ };
637
+ };
638
+ const complete = async (chain, resourceType, pathChildren, context)=>{
639
+ if (chain.path.length > 0) {
640
+ await resolveNestedTypes(pathChildren, resourceType, chain.path, ctx.fetchSchema);
641
+ }
642
+ if (isArrayPosition(chain, pathChildren, resourceType)) {
643
+ return buildArrayIndexResult(chain, context);
644
+ }
645
+ return buildJsonbResult(chain, pathChildren, resourceType, context);
646
+ };
647
+ const source = (context)=>{
648
+ const info = resolveChain(context);
649
+ if (!info) return null;
650
+ const { chain, resourceType } = info;
651
+ const cached = ctx.sdCache[resourceType];
652
+ if (cached) {
653
+ if (chain.path.length === 0) {
654
+ return buildJsonbResult(chain, cached, resourceType, context);
655
+ }
656
+ return complete(chain, resourceType, cached, context);
657
+ }
658
+ return ctx.fetchSchema(resourceType).then((fetched)=>{
659
+ if (!fetched) return null;
660
+ return complete(chain, resourceType, fetched, context);
661
+ });
662
+ };
663
+ return EditorState.languageData.of(()=>[
664
+ {
665
+ autocomplete: source
666
+ }
667
+ ]);
668
+ }
669
+ function sqlCompletionOverride() {
670
+ return autocompletion({
671
+ override: [
672
+ async (context)=>{
673
+ const line = context.state.doc.lineAt(context.pos);
674
+ const textBefore = line.text.slice(0, context.pos - line.from);
675
+ const inJsonb = isInJsonbContext(textBefore);
676
+ const langSources = context.state.languageDataAt("autocomplete", context.pos);
677
+ const results = (await Promise.all(langSources.map((src)=>Promise.resolve(src(context))))).filter((r)=>r !== null);
678
+ if (results.length === 0) return null;
679
+ if (inJsonb) {
680
+ const jsonbTypes = new Set([
681
+ "property",
682
+ "enum",
683
+ "operator"
684
+ ]);
685
+ const jsonbResult = results.find((r)=>r.options.some((o)=>jsonbTypes.has(o.type ?? "")));
686
+ if (!jsonbResult) return null;
687
+ return {
688
+ ...jsonbResult,
689
+ options: jsonbResult.options.filter((o)=>jsonbTypes.has(o.type ?? ""))
690
+ };
691
+ }
692
+ const hasTableResults = results.some((r)=>r.options.some((o)=>o.type === "table"));
693
+ if (hasTableResults) {
694
+ const tableOptions = results.flatMap((r)=>r.options.filter((o)=>o.type === "table" || o.type === "keyword"));
695
+ const from = results.find((r)=>r.options.some((o)=>o.type === "table"))?.from;
696
+ if (from == null) return null;
697
+ return {
698
+ from,
699
+ options: tableOptions
700
+ };
701
+ }
702
+ if (results.length === 1) return results[0];
703
+ const groups = new Map();
704
+ for (const r of results){
705
+ const existing = groups.get(r.from);
706
+ if (existing) {
707
+ existing.options.push(...r.options);
708
+ } else {
709
+ groups.set(r.from, {
710
+ from: r.from,
711
+ options: [
712
+ ...r.options
713
+ ]
714
+ });
715
+ }
716
+ }
717
+ let best = null;
718
+ for (const g of groups.values()){
719
+ if (!best || g.options.length > best.options.length) best = g;
720
+ }
721
+ if (best) {
722
+ best.options = best.options.map((o)=>{
723
+ if (o.type === "keyword") return {
724
+ ...o,
725
+ boost: 2
726
+ };
727
+ if (o.type === "type") return {
728
+ ...o,
729
+ boost: 1
730
+ };
731
+ if (o.type === "variable") return {
732
+ ...o,
733
+ boost: -1
734
+ };
735
+ return o;
736
+ });
737
+ }
738
+ return best;
739
+ }
740
+ ]
741
+ });
742
+ }
743
+ // ── Public API ──
744
+ export async function fetchSqlMetadata(executeSql) {
745
+ const [tablesRows, jsonbRows, functionsRows, columnsRows] = await Promise.all([
746
+ executeSql(TABLES_QUERY, "tables"),
747
+ executeSql(JSONB_COLUMNS_QUERY, "jsonb_columns"),
748
+ executeSql(FUNCTIONS_QUERY, "functions"),
749
+ executeSql(COLUMNS_QUERY, "columns")
750
+ ]);
751
+ const schemas = {};
752
+ for (const row of tablesRows){
753
+ const s = String(row.table_schema);
754
+ if (!schemas[s]) schemas[s] = [];
755
+ schemas[s].push(String(row.table_name));
756
+ }
757
+ const jsonbColumns = {};
758
+ for (const row of jsonbRows){
759
+ const key = `${row.table_schema}.${row.table_name}`;
760
+ if (!jsonbColumns[key]) jsonbColumns[key] = [];
761
+ jsonbColumns[key].push(String(row.column_name));
762
+ }
763
+ const functions = functionsRows.map((r)=>String(r.name));
764
+ const columns = {};
765
+ for (const row of columnsRows){
766
+ const key = `${row.table_schema}.${row.table_name}`;
767
+ if (!columns[key]) columns[key] = [];
768
+ columns[key].push({
769
+ name: String(row.column_name),
770
+ dataType: String(row.data_type)
771
+ });
772
+ }
773
+ return {
774
+ schemas,
775
+ jsonbColumns,
776
+ functions,
777
+ columns
778
+ };
779
+ }
780
+ export function buildSqlCompletionExtensions(metadata, executeSql) {
781
+ const sdCache = {};
782
+ const sdNotFound = new Set();
783
+ const fetchSchema = async (resourceType)=>{
784
+ if (sdCache[resourceType]) return sdCache[resourceType];
785
+ if (sdNotFound.has(resourceType)) return null;
786
+ try {
787
+ const name = resourceType.replace(/'/g, "''");
788
+ const rows = await executeSql(`SELECT resource FROM far.canonicalresource WHERE rt = 'StructureDefinition' AND resource->>'name' = '${name}' LIMIT 1`, "structure_definition");
789
+ const sd = rows[0]?.resource ?? null;
790
+ if (!sd) {
791
+ sdNotFound.add(resourceType);
792
+ return null;
793
+ }
794
+ const pathChildren = buildFromStructureDefinition(sd);
795
+ transformReferenceFields(pathChildren);
796
+ sdCache[resourceType] = pathChildren;
797
+ return pathChildren;
798
+ } catch {
799
+ sdNotFound.add(resourceType);
800
+ return null;
801
+ }
802
+ };
803
+ return [
804
+ EditorView.theme({
805
+ ".cm-tooltip.cm-tooltip-autocomplete": {
806
+ background: "var(--color-bg-primary)",
807
+ border: "1px solid var(--color-border-primary)",
808
+ borderRadius: "var(--radius-md)",
809
+ padding: "4px",
810
+ boxShadow: "0 4px 12px rgba(0, 0, 0, 0.1)",
811
+ fontFamily: "var(--font-family-sans)",
812
+ fontSize: "14px"
813
+ },
814
+ ".cm-tooltip.cm-tooltip-autocomplete > ul": {
815
+ maxHeight: "300px"
816
+ },
817
+ ".cm-tooltip-autocomplete ul li": {
818
+ padding: "4px 8px",
819
+ borderRadius: "4px"
820
+ },
821
+ ".cm-tooltip-autocomplete ul li[aria-selected]": {
822
+ background: "var(--color-bg-quaternary)",
823
+ color: "var(--color-text-primary)"
824
+ },
825
+ ".cm-completionLabel": {
826
+ color: "var(--color-text-primary)",
827
+ fontSize: "14px"
828
+ },
829
+ ".cm-completionDetail": {
830
+ color: "var(--color-text-tertiary)",
831
+ fontSize: "12px",
832
+ fontStyle: "normal",
833
+ marginLeft: "8px"
834
+ },
835
+ ".cm-completionIcon": {
836
+ padding: "0",
837
+ marginRight: "6px",
838
+ width: "18px",
839
+ height: "18px",
840
+ display: "inline-flex",
841
+ alignItems: "center",
842
+ justifyContent: "center",
843
+ borderRadius: "4px",
844
+ fontSize: "11px",
845
+ fontWeight: "600",
846
+ lineHeight: "1",
847
+ boxSizing: "border-box"
848
+ },
849
+ ".cm-completionIcon-table": {
850
+ background: "var(--color-blue-100)",
851
+ color: "var(--color-blue-600)"
852
+ },
853
+ ".cm-completionIcon-table::after": {
854
+ content: "'T'"
855
+ },
856
+ ".cm-completionIcon-keyword": {
857
+ background: "var(--color-green-200)",
858
+ color: "var(--color-green-700)"
859
+ },
860
+ ".cm-completionIcon-keyword::after": {
861
+ content: "'S'"
862
+ },
863
+ ".cm-completionIcon-property": {
864
+ background: "var(--color-purple-100)",
865
+ color: "var(--color-purple-600)"
866
+ },
867
+ ".cm-completionIcon-property::after": {
868
+ content: "'F'"
869
+ },
870
+ ".cm-completionIcon-variable": {
871
+ background: "var(--color-yellow-200)",
872
+ color: "var(--color-yellow-700)"
873
+ },
874
+ ".cm-completionIcon-variable::after": {
875
+ content: "'C'"
876
+ }
877
+ }),
878
+ tableCompletionExtension(metadata.schemas),
879
+ columnCompletionExtension({
880
+ schemas: metadata.schemas,
881
+ columns: metadata.columns
882
+ }),
883
+ jsonbCompletionExtension({
884
+ schemas: metadata.schemas,
885
+ jsonbColumns: metadata.jsonbColumns,
886
+ sdCache,
887
+ sdNotFound,
888
+ fetchSchema
889
+ }),
890
+ jsonbOperatorExtension(),
891
+ sqlCompletionOverride()
892
+ ];
893
+ }
894
+
895
+ //# sourceMappingURL=sql-completion.js.map