@rozenite/sqlite-plugin 1.7.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/LICENSE +20 -0
  3. package/README.md +102 -0
  4. package/dist/devtools/assets/panel-B3paLkwG.js +82 -0
  5. package/dist/devtools/assets/panel-CIU0JBOs.css +1 -0
  6. package/dist/devtools/panel.html +31 -0
  7. package/dist/react-native/chunks/bridge-values.cjs +5 -0
  8. package/dist/react-native/chunks/bridge-values.js +258 -0
  9. package/dist/react-native/chunks/index.require.cjs +1 -0
  10. package/dist/react-native/chunks/index.require.js +118 -0
  11. package/dist/react-native/chunks/useRozeniteSqlitePlugin.require.cjs +1 -0
  12. package/dist/react-native/chunks/useRozeniteSqlitePlugin.require.js +189 -0
  13. package/dist/react-native/index.cjs +1 -0
  14. package/dist/react-native/index.d.ts +178 -0
  15. package/dist/react-native/index.js +16 -0
  16. package/dist/rozenite.json +1 -0
  17. package/package.json +83 -0
  18. package/postcss.config.js +6 -0
  19. package/react-native.ts +55 -0
  20. package/rozenite.config.ts +8 -0
  21. package/src/react-native/adapters/__tests__/expo-sqlite.test.ts +94 -0
  22. package/src/react-native/adapters/expo-sqlite.ts +230 -0
  23. package/src/react-native/adapters/generic.ts +88 -0
  24. package/src/react-native/adapters/index.ts +9 -0
  25. package/src/react-native/sqlite-view.ts +24 -0
  26. package/src/react-native/useRozeniteSqlitePlugin.ts +262 -0
  27. package/src/shared/__tests__/bridge-values.test.ts +34 -0
  28. package/src/shared/__tests__/sql.test.ts +55 -0
  29. package/src/shared/bridge-values.ts +170 -0
  30. package/src/shared/protocol.ts +41 -0
  31. package/src/shared/sql.ts +420 -0
  32. package/src/shared/types.ts +81 -0
  33. package/src/ui/__tests__/sql-editor-utils.test.ts +135 -0
  34. package/src/ui/__tests__/sqlite-row-edit-value.test.ts +22 -0
  35. package/src/ui/__tests__/sqlite-row-mutations.test.ts +310 -0
  36. package/src/ui/__tests__/sqlite-table-column-order.test.ts +83 -0
  37. package/src/ui/__tests__/value-utils.test.tsx +12 -0
  38. package/src/ui/cell-detail-drawer.tsx +65 -0
  39. package/src/ui/globals.css +1415 -0
  40. package/src/ui/panel.tsx +2815 -0
  41. package/src/ui/query-result-table.tsx +199 -0
  42. package/src/ui/sql-editor-utils.ts +352 -0
  43. package/src/ui/sql-editor.tsx +509 -0
  44. package/src/ui/sqlite-data-table.tsx +296 -0
  45. package/src/ui/sqlite-introspection.ts +189 -0
  46. package/src/ui/sqlite-modal-controls.tsx +32 -0
  47. package/src/ui/sqlite-row-delete-modal.tsx +130 -0
  48. package/src/ui/sqlite-row-edit-modal.tsx +487 -0
  49. package/src/ui/sqlite-row-edit-value.ts +53 -0
  50. package/src/ui/sqlite-row-mutations.ts +246 -0
  51. package/src/ui/sqlite-table-column-order.ts +154 -0
  52. package/src/ui/use-sqlite-requests.ts +205 -0
  53. package/src/ui/utils.ts +107 -0
  54. package/src/ui/value-utils.tsx +162 -0
  55. package/tsconfig.json +36 -0
  56. package/vite.config.ts +20 -0
@@ -0,0 +1,509 @@
1
+ import {
2
+ autocompletion,
3
+ closeBrackets,
4
+ closeBracketsKeymap,
5
+ completionKeymap,
6
+ type CompletionSource,
7
+ } from '@codemirror/autocomplete';
8
+ import {
9
+ defaultKeymap,
10
+ history,
11
+ historyKeymap,
12
+ indentWithTab,
13
+ } from '@codemirror/commands';
14
+ import {
15
+ schemaCompletionSource,
16
+ sql,
17
+ SQLite,
18
+ type SQLNamespace,
19
+ } from '@codemirror/lang-sql';
20
+ import { HighlightStyle, syntaxHighlighting } from '@codemirror/language';
21
+ import { tags } from '@lezer/highlight';
22
+ import {
23
+ Compartment,
24
+ EditorSelection,
25
+ EditorState,
26
+ type Extension,
27
+ } from '@codemirror/state';
28
+ import { searchKeymap } from '@codemirror/search';
29
+ import {
30
+ Decoration,
31
+ EditorView,
32
+ drawSelection,
33
+ highlightActiveLine,
34
+ highlightActiveLineGutter,
35
+ keymap,
36
+ lineNumbers,
37
+ placeholder,
38
+ } from '@codemirror/view';
39
+ import {
40
+ forwardRef,
41
+ useEffect,
42
+ useImperativeHandle,
43
+ useRef,
44
+ type Ref,
45
+ } from 'react';
46
+
47
+ export type SqlEditorHandle = {
48
+ focus: () => void;
49
+ getSelection: () => { start: number; end: number };
50
+ };
51
+
52
+ type SqlEditorProps = {
53
+ ariaLabel: string;
54
+ completionSchema: SQLNamespace;
55
+ completionSource?: CompletionSource;
56
+ defaultSchema?: string;
57
+ defaultTable?: string;
58
+ errorLine: number | null;
59
+ onFormat: () => void;
60
+ onRun: () => void;
61
+ onRunCurrent: () => void;
62
+ onSave: () => void;
63
+ onSelectionChange: (selection: { start: number; end: number }) => void;
64
+ onValueChange: (value: string) => void;
65
+ placeholderText: string;
66
+ readOnly?: boolean;
67
+ value: string;
68
+ };
69
+
70
+ const sqlSupportCompartment = new Compartment();
71
+ const autocompleteCompartment = new Compartment();
72
+ const errorLineCompartment = new Compartment();
73
+ const placeholderCompartment = new Compartment();
74
+ const editableCompartment = new Compartment();
75
+
76
+ const emptyDecorations = Decoration.set([]);
77
+ const sqlHighlightStyle = HighlightStyle.define([
78
+ {
79
+ tag: [tags.keyword, tags.operatorKeyword],
80
+ color: '#8ec5ff',
81
+ fontWeight: '600',
82
+ },
83
+ {
84
+ tag: [tags.string, tags.special(tags.string)],
85
+ color: '#8fe0ba',
86
+ },
87
+ {
88
+ tag: [tags.number, tags.integer, tags.float, tags.bool],
89
+ color: '#f9c97a',
90
+ },
91
+ {
92
+ tag: [tags.comment, tags.lineComment, tags.blockComment],
93
+ color: 'rgba(165, 185, 204, 0.56)',
94
+ fontStyle: 'italic',
95
+ },
96
+ {
97
+ tag: [tags.name, tags.variableName, tags.propertyName],
98
+ color: '#edf5fb',
99
+ },
100
+ {
101
+ tag: [tags.definition(tags.name), tags.typeName, tags.namespace],
102
+ color: '#9ecbff',
103
+ },
104
+ {
105
+ tag: [tags.function(tags.variableName), tags.function(tags.propertyName)],
106
+ color: '#7fd8ff',
107
+ },
108
+ {
109
+ tag: [tags.operator, tags.compareOperator, tags.logicOperator],
110
+ color: 'rgba(190, 216, 238, 0.88)',
111
+ },
112
+ {
113
+ tag: tags.null,
114
+ color: '#ff9f7f',
115
+ },
116
+ {
117
+ tag: [tags.paren, tags.squareBracket, tags.brace, tags.punctuation],
118
+ color: 'rgba(226, 236, 245, 0.82)',
119
+ },
120
+ ]);
121
+
122
+ const sqlEditorTheme = EditorView.theme(
123
+ {
124
+ '&': {
125
+ display: 'flex',
126
+ flex: '1 1 auto',
127
+ minHeight: '0',
128
+ height: '100%',
129
+ color: 'var(--sqlite-text)',
130
+ backgroundColor: 'transparent',
131
+ fontSize: '0.8rem',
132
+ },
133
+ '&.cm-focused': {
134
+ outline: 'none',
135
+ },
136
+ '.cm-scroller': {
137
+ display: 'flex',
138
+ flex: '1 1 auto',
139
+ minHeight: '0',
140
+ fontFamily:
141
+ "'IBM Plex Mono', 'JetBrains Mono', 'SFMono-Regular', ui-monospace, monospace",
142
+ },
143
+ '.cm-sizer': {
144
+ minHeight: '100%',
145
+ minWidth: '100%',
146
+ flex: '1 1 auto',
147
+ },
148
+ '.cm-content': {
149
+ minHeight: '100%',
150
+ caretColor: '#f7fbff',
151
+ },
152
+ '.cm-cursor, .cm-dropCursor': {
153
+ borderLeftColor: '#f7fbff',
154
+ },
155
+ '.cm-selectionBackground, &.cm-focused .cm-selectionBackground, ::selection':
156
+ {
157
+ backgroundColor: 'rgba(89, 163, 255, 0.2)',
158
+ },
159
+ '.cm-activeLine': {
160
+ backgroundColor: 'rgba(255, 255, 255, 0.032)',
161
+ },
162
+ '.cm-activeLineGutter': {
163
+ backgroundColor: 'rgba(255, 255, 255, 0.028)',
164
+ color: 'rgba(244, 249, 253, 0.96)',
165
+ },
166
+ '.cm-gutters': {
167
+ color: 'rgba(165, 185, 204, 0.5)',
168
+ backgroundColor: 'rgba(255, 255, 255, 0.02)',
169
+ borderRight: '1px solid var(--sqlite-line)',
170
+ },
171
+ '.cm-tooltip': {
172
+ border: '1px solid var(--sqlite-line-strong)',
173
+ backgroundColor: 'rgba(10, 20, 31, 0.98)',
174
+ color: 'var(--sqlite-text)',
175
+ borderRadius: '0.9rem',
176
+ boxShadow: '0 20px 52px rgba(0, 0, 0, 0.34)',
177
+ overflow: 'hidden',
178
+ },
179
+ '.cm-tooltip-autocomplete > ul > li[aria-selected]': {
180
+ backgroundColor: 'rgba(89, 163, 255, 0.18)',
181
+ color: '#fff',
182
+ },
183
+ '.cm-panels': {
184
+ backgroundColor: 'rgba(255, 255, 255, 0.02)',
185
+ color: 'var(--sqlite-text)',
186
+ borderBottom: '1px solid var(--sqlite-line)',
187
+ },
188
+ '.cm-searchMatch': {
189
+ backgroundColor: 'rgba(255, 191, 105, 0.2)',
190
+ outline: '1px solid rgba(255, 191, 105, 0.22)',
191
+ },
192
+ '.cm-searchMatch.cm-searchMatch-selected': {
193
+ backgroundColor: 'rgba(89, 163, 255, 0.26)',
194
+ },
195
+ '.cm-matchingBracket, .cm-nonmatchingBracket': {
196
+ backgroundColor: 'rgba(255, 255, 255, 0.06)',
197
+ outline: '1px solid rgba(255, 255, 255, 0.08)',
198
+ },
199
+ },
200
+ { dark: true },
201
+ );
202
+
203
+ const createErrorLineExtension = (errorLine: number | null): Extension => {
204
+ if (!errorLine) {
205
+ return EditorView.decorations.of(emptyDecorations);
206
+ }
207
+
208
+ return EditorView.decorations.of((view) => {
209
+ if (errorLine < 1 || errorLine > view.state.doc.lines) {
210
+ return emptyDecorations;
211
+ }
212
+
213
+ const line = view.state.doc.line(errorLine);
214
+ return Decoration.set([
215
+ Decoration.line({
216
+ attributes: { class: 'cm-sqlite-errorLine' },
217
+ }).range(line.from),
218
+ ]);
219
+ });
220
+ };
221
+
222
+ const createAutocompleteExtension = ({
223
+ completionSchema,
224
+ completionSource,
225
+ defaultSchema,
226
+ defaultTable,
227
+ }: Pick<
228
+ SqlEditorProps,
229
+ 'completionSchema' | 'completionSource' | 'defaultSchema' | 'defaultTable'
230
+ >): Extension => {
231
+ const sources = [
232
+ completionSource,
233
+ schemaCompletionSource({
234
+ defaultSchema,
235
+ defaultTable,
236
+ dialect: SQLite,
237
+ schema: completionSchema,
238
+ upperCaseKeywords: true,
239
+ }),
240
+ ].filter(Boolean) as CompletionSource[];
241
+
242
+ return autocompletion({
243
+ activateOnTyping: true,
244
+ override: sources,
245
+ });
246
+ };
247
+
248
+ const createEditableExtension = (readOnly: boolean): Extension => [
249
+ EditorState.readOnly.of(readOnly),
250
+ EditorView.editable.of(!readOnly),
251
+ ];
252
+
253
+ const SqlEditorInner = (
254
+ {
255
+ ariaLabel,
256
+ completionSchema,
257
+ completionSource,
258
+ defaultSchema,
259
+ defaultTable,
260
+ errorLine,
261
+ onFormat,
262
+ onRun,
263
+ onRunCurrent,
264
+ onSave,
265
+ onSelectionChange,
266
+ onValueChange,
267
+ placeholderText,
268
+ readOnly = false,
269
+ value,
270
+ }: SqlEditorProps,
271
+ ref: Ref<SqlEditorHandle>,
272
+ ) => {
273
+ const hostRef = useRef<HTMLDivElement | null>(null);
274
+ const viewRef = useRef<EditorView | null>(null);
275
+ const onValueChangeRef = useRef(onValueChange);
276
+ const onSelectionChangeRef = useRef(onSelectionChange);
277
+ const onRunRef = useRef(onRun);
278
+ const onRunCurrentRef = useRef(onRunCurrent);
279
+ const onSaveRef = useRef(onSave);
280
+ const onFormatRef = useRef(onFormat);
281
+ const applyingExternalValueRef = useRef(false);
282
+ const initialValueRef = useRef(value);
283
+ const initialConfigRef = useRef({
284
+ ariaLabel,
285
+ completionSchema,
286
+ completionSource,
287
+ defaultSchema,
288
+ defaultTable,
289
+ errorLine,
290
+ placeholderText,
291
+ readOnly,
292
+ });
293
+
294
+ useEffect(() => {
295
+ onValueChangeRef.current = onValueChange;
296
+ }, [onValueChange]);
297
+
298
+ useEffect(() => {
299
+ onSelectionChangeRef.current = onSelectionChange;
300
+ }, [onSelectionChange]);
301
+
302
+ useEffect(() => {
303
+ onRunRef.current = onRun;
304
+ }, [onRun]);
305
+
306
+ useEffect(() => {
307
+ onRunCurrentRef.current = onRunCurrent;
308
+ }, [onRunCurrent]);
309
+
310
+ useEffect(() => {
311
+ onSaveRef.current = onSave;
312
+ }, [onSave]);
313
+
314
+ useEffect(() => {
315
+ onFormatRef.current = onFormat;
316
+ }, [onFormat]);
317
+
318
+ useImperativeHandle(
319
+ ref,
320
+ () => ({
321
+ focus: () => {
322
+ viewRef.current?.focus();
323
+ },
324
+ getSelection: () => {
325
+ const selection = viewRef.current?.state.selection.main;
326
+ return {
327
+ start: selection?.from ?? 0,
328
+ end: selection?.to ?? 0,
329
+ };
330
+ },
331
+ }),
332
+ [],
333
+ );
334
+
335
+ useEffect(() => {
336
+ const host = hostRef.current;
337
+ if (!host) {
338
+ return;
339
+ }
340
+
341
+ const editorView = new EditorView({
342
+ parent: host,
343
+ state: EditorState.create({
344
+ doc: initialValueRef.current,
345
+ extensions: [
346
+ sqlEditorTheme,
347
+ EditorView.contentAttributes.of({
348
+ 'aria-label': initialConfigRef.current.ariaLabel,
349
+ }),
350
+ EditorView.updateListener.of((update) => {
351
+ if (update.docChanged && !applyingExternalValueRef.current) {
352
+ onValueChangeRef.current(update.state.doc.toString());
353
+ }
354
+
355
+ if (update.selectionSet || update.docChanged) {
356
+ const selection = update.state.selection.main;
357
+ onSelectionChangeRef.current({
358
+ start: selection.from,
359
+ end: selection.to,
360
+ });
361
+ }
362
+ }),
363
+ lineNumbers(),
364
+ highlightActiveLineGutter(),
365
+ highlightActiveLine(),
366
+ drawSelection(),
367
+ history(),
368
+ closeBrackets(),
369
+ syntaxHighlighting(sqlHighlightStyle),
370
+ sqlSupportCompartment.of(
371
+ sql({ dialect: SQLite, upperCaseKeywords: true }),
372
+ ),
373
+ autocompleteCompartment.of(
374
+ createAutocompleteExtension({
375
+ completionSchema: initialConfigRef.current.completionSchema,
376
+ completionSource: initialConfigRef.current.completionSource,
377
+ defaultSchema: initialConfigRef.current.defaultSchema,
378
+ defaultTable: initialConfigRef.current.defaultTable,
379
+ }),
380
+ ),
381
+ errorLineCompartment.of(
382
+ createErrorLineExtension(initialConfigRef.current.errorLine),
383
+ ),
384
+ placeholderCompartment.of(
385
+ placeholder(initialConfigRef.current.placeholderText),
386
+ ),
387
+ editableCompartment.of(
388
+ createEditableExtension(initialConfigRef.current.readOnly),
389
+ ),
390
+ keymap.of([
391
+ {
392
+ key: 'Mod-Enter',
393
+ run: () => {
394
+ onRunRef.current();
395
+ return true;
396
+ },
397
+ },
398
+ {
399
+ key: 'Shift-Mod-Enter',
400
+ run: () => {
401
+ onRunCurrentRef.current();
402
+ return true;
403
+ },
404
+ },
405
+ {
406
+ key: 'Mod-s',
407
+ run: () => {
408
+ onSaveRef.current();
409
+ return true;
410
+ },
411
+ },
412
+ {
413
+ key: 'Shift-Alt-f',
414
+ run: () => {
415
+ onFormatRef.current();
416
+ return true;
417
+ },
418
+ },
419
+ indentWithTab,
420
+ ...closeBracketsKeymap,
421
+ ...completionKeymap,
422
+ ...historyKeymap,
423
+ ...searchKeymap,
424
+ ...defaultKeymap,
425
+ ]),
426
+ ],
427
+ }),
428
+ });
429
+
430
+ viewRef.current = editorView;
431
+ onSelectionChangeRef.current({
432
+ start: editorView.state.selection.main.from,
433
+ end: editorView.state.selection.main.to,
434
+ });
435
+
436
+ return () => {
437
+ viewRef.current = null;
438
+ editorView.destroy();
439
+ };
440
+ }, []);
441
+
442
+ useEffect(() => {
443
+ const editorView = viewRef.current;
444
+ if (!editorView) {
445
+ return;
446
+ }
447
+
448
+ editorView.dispatch({
449
+ effects: [
450
+ sqlSupportCompartment.reconfigure(
451
+ sql({ dialect: SQLite, upperCaseKeywords: true }),
452
+ ),
453
+ autocompleteCompartment.reconfigure(
454
+ createAutocompleteExtension({
455
+ completionSchema,
456
+ completionSource,
457
+ defaultSchema,
458
+ defaultTable,
459
+ }),
460
+ ),
461
+ errorLineCompartment.reconfigure(createErrorLineExtension(errorLine)),
462
+ placeholderCompartment.reconfigure(placeholder(placeholderText)),
463
+ editableCompartment.reconfigure(createEditableExtension(readOnly)),
464
+ ],
465
+ });
466
+ }, [
467
+ completionSchema,
468
+ completionSource,
469
+ defaultSchema,
470
+ defaultTable,
471
+ errorLine,
472
+ placeholderText,
473
+ readOnly,
474
+ ]);
475
+
476
+ useEffect(() => {
477
+ const editorView = viewRef.current;
478
+ if (!editorView) {
479
+ return;
480
+ }
481
+
482
+ const currentValue = editorView.state.doc.toString();
483
+ if (currentValue === value) {
484
+ return;
485
+ }
486
+
487
+ const selection = editorView.state.selection.main;
488
+ const nextSelection = EditorSelection.single(
489
+ Math.min(selection.anchor, value.length),
490
+ Math.min(selection.head, value.length),
491
+ );
492
+
493
+ applyingExternalValueRef.current = true;
494
+ editorView.dispatch({
495
+ changes: {
496
+ from: 0,
497
+ to: currentValue.length,
498
+ insert: value,
499
+ },
500
+ selection: nextSelection,
501
+ });
502
+ applyingExternalValueRef.current = false;
503
+ }, [value]);
504
+
505
+ return <div ref={hostRef} className="sqlite-editor-shell" />;
506
+ };
507
+
508
+ export const SqlEditor = forwardRef(SqlEditorInner);
509
+ SqlEditor.displayName = 'SqlEditor';