rune-lab 0.0.7 โ†’ 0.0.8

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.
package/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <h1 align="center">
2
- <img src="https://raw.githubusercontent.com/Yrrrrrf/rune-lab/main/static/rune.png" alt="Rune Lab Icon" width="128" height="128" description="Some rune that represents the Svelte rune system">
2
+ <img src="https://raw.githubusercontent.com/Yrrrrrf/rune-lab/main/static/rune.png" alt="Rune Lab Icon" width="128" height="128" description="Icon representing the Svelte Runes system">
3
3
  <div align="center">Rune Lab</div>
4
4
  </h1>
5
5
 
@@ -14,48 +14,61 @@
14
14
 
15
15
  ## Overview
16
16
 
17
- Rune Lab is a modern component library built with Svelte 5, focused on providing powerful,
18
- reactive UI components using Svelte's runes system. It offers a comprehensive set of components,
19
- theming capabilities, and utilities for building modern web applications.
17
+ **Rune Lab** is your modern toolkit for crafting stunning, reactive web applications with
18
+ **Svelte 5**. Harnessing the power of Svelte's new **Runes** system, Rune Lab offers a suite of
19
+ elegant UI components designed for seamless data handling and beautiful theming.
20
20
 
21
- ## Features
21
+ It's built to integrate effortlessly with your data sources, especially shining when connected
22
+ to the [prism-py](https://github.com/Yrrrrrf/prism-py) and
23
+ [prism-ts](https://github.com/Yrrrrrf/prism-ts) ecosystem for end-to-end type-safe API
24
+ interactions.
22
25
 
23
- - **๐Ÿงช Svelte 5 Runes**: Built with Svelte 5's powerful reactivity primitives
24
- - **๐ŸŽจ Theme System**: Extensive theming with DaisyUI integration
25
- - **๐Ÿ”„ Type Safety**: Full TypeScript support with strong typing
26
- - **๐Ÿ“Š Data Visualization**: Components for visualizing complex data
27
- - **๐ŸŒ API Integration**: Tools for type-safe API interactions
28
- - **๐Ÿ“ฆ Zero Dependencies**: Lightweight core with optional integrations
29
- - **๐Ÿฆ• Deno & NPM**: Available on both JSR and NPM
26
+ ## Key Features
27
+
28
+ - **โœจ Svelte 5 Runes Core:** Experience fine-grained reactivity and cleaner component logic.
29
+ - **๐ŸŽจ Dynamic Theming:** Powered by DaisyUI & Tailwind CSS for extensive customization and
30
+ out-of-the-box themes.
31
+ - **๐Ÿ”’ TypeScript First:** Robust type-safety for a confident and productive development
32
+ workflow.
33
+ - **๐Ÿ“Š Data-Aware Components:** Tools and components built to handle and visualize complex data.
34
+ - **๐Ÿ›ฐ๏ธ Interactive Schema Explorer:** A standout feature! Visually explore and interact with
35
+ database schemas exposed by `prism-py` APIs directly within your Svelte application. Test CRUD
36
+ operations, execute functions, and understand your data structure like never before.
37
+ - **๐ŸŒ Smart API Integration:** Includes `apiStore` (using `prism-ts`) for easy and type-safe
38
+ connection to backend APIs.
39
+ - **๐Ÿ“ฆ Lightweight Core:** Designed to be lean, with optional integrations.
40
+ - **๐Ÿฆ• Universal Access:** Available on JSR (for Deno) and NPM (for Node.js/Bun/Yarn).
41
+
42
+ ## The Prism Ecosystem Advantage
43
+
44
+ Rune Lab is designed to be a perfect companion to the Prism ecosystem:
45
+
46
+ - **[prism-py](https://github.com/Yrrrrrf/prism-py):** Automatically generates REST APIs from
47
+ your database schema.
48
+ - **[prism-ts](https://github.com/Yrrrrrf/prism-ts):** A TypeScript client that consumes these
49
+ APIs with full type-safety.
50
+
51
+ When used together, Rune Lab's API integration tools (like the `apiStore` and
52
+ `RLSchemaExplorer`) provide a remarkably streamlined and type-safe path from your backend data
53
+ to your frontend UI.
30
54
 
31
55
  ## Installation
32
56
 
33
- ### Using Deno / JSR
57
+ ### Using Deno / [JSR](https://jsr.io/@yrrrrrf/rune-lab)
34
58
 
35
59
  ```bash
36
60
  # Add to your Deno project
37
61
  deno add @yrrrrrf/rune-lab
38
62
  ```
39
63
 
40
- ### Using NPM / Bun / Yarn
64
+ ### Using [NPM](https://www.npmjs.com/package/rune-lab) / Bun / Yarn
41
65
 
42
66
  ```bash
43
- # NPM
44
67
  npm install rune-lab
45
-
46
- # Bun
47
68
  bun add rune-lab
48
-
49
- # Yarn
50
69
  yarn add rune-lab
51
70
  ```
52
71
 
53
72
  ## License
54
73
 
55
74
  MIT License - See [LICENSE](LICENSE) for details.
56
-
57
- ---
58
-
59
- <div align="center">
60
- Built with โค๏ธ using Svelte 5 and Deno
61
- </div>
@@ -1,6 +1,7 @@
1
1
  <!-- src/lib/components/dataview/RLMetadataTable.svelte -->
2
2
  <script lang="ts">
3
3
  import type { ColumnMetadata, ColumnReference, EnumMetadata } from '@yrrrrrf/prism-ts';
4
+ import { formatReferenceText } from '../../tools/form-helpers.js';
4
5
 
5
6
  let {
6
7
  title,
@@ -18,10 +19,6 @@
18
19
  onEnumClick?: (enumData: { name: string; values: string[] }) => void; // Make optional
19
20
  }>();
20
21
 
21
- function formatReferenceText(ref: ColumnReference | undefined): string {
22
- if (!ref) return '';
23
- return `${ref.schema}.${ref.table}.${ref.column}`;
24
- }
25
22
 
26
23
  function handleFkButtonClick(ref: ColumnReference | undefined) {
27
24
  if (ref && onFkClick) {
@@ -1,15 +1,10 @@
1
1
  <!-- src/lib/components/explorer/RLSchemaExplorer.svelte -->
2
- // src/lib/components/explorer/RLSchemaExplorer.svelte
3
2
  <script lang="ts">
4
3
  import type {
5
- SchemaMetadata,
6
- TableMetadata,
7
- ViewMetadata,
8
- EnumMetadata,
9
- FunctionMetadata,
10
- ColumnMetadata,
11
- ColumnReference,
12
- FunctionParameter,
4
+ SchemaMetadata as PrismSchemaMetadata, // Original type from prism-ts
5
+ // Other prism-ts types might be needed if used directly for options in openModalForApi
6
+ ColumnMetadata as PrismColumnMetadata,
7
+ FunctionParameter as PrismFunctionParameter,
13
8
  } from '@yrrrrrf/prism-ts';
14
9
  import { apiStore } from '../stores/api.svelte';
15
10
  import { explorerStore, type ExplorerEntityType as StoreExplorerEntityType } from '../stores/explorer.svelte';
@@ -17,14 +12,14 @@
17
12
  import RLApiInterface from '../api/RLApiInterface.svelte';
18
13
  import RLApiOperationModal from '../api/RLApiOperationModal.svelte';
19
14
 
20
- // If EntityType is used in the template, it can be aliased from the store's export
21
- // type EntityType = StoreExplorerEntityType;
22
- // Or if not used in template, no need for this alias here.
15
+ // Import Rune Lab specific types and the transformer
16
+ import type { RLSchemaData, RLTableMetadata, RLViewMetadata, RLEnumMetadata, RLFunctionMetadata, RLColumnMetadata, RLColumnReference } from '../stores/explorer.svelte';
17
+ import { transformPrismSchemasToRLData } from '../../tools/schema-transformer.js'; // Correct path to tools index
23
18
 
24
19
  type APIOperation = 'GET' | 'POST' | 'PUT' | 'DELETE';
25
20
  type ModalResourceType = 'table' | 'view' | 'function';
26
21
 
27
- let schemas = $state<SchemaMetadata[] | null>(null);
22
+ let schemas = $state<RLSchemaData[] | null>(null); // Use RLSchemaData here
28
23
  let isLoading = $state(true);
29
24
  let error = $state<string | null>(null);
30
25
 
@@ -33,9 +28,12 @@
33
28
  let modalTargetSchemaName = $state<string>('');
34
29
  let modalTargetResourceName = $state<string>('');
35
30
  let modalTargetResourceType = $state<ModalResourceType>('table');
36
- let modalTargetColumns = $state<ColumnMetadata[]>([]);
31
+ let modalTargetColumns = $state<RLColumnMetadata[]>([]); // Use RLColumnMetadata
37
32
  let modalTargetOperation = $state<APIOperation>('GET');
38
- let modalTargetFunctionParams = $state<FunctionMetadata['parameters'] | null>(null);
33
+ // For function parameters, the RLApiOperationModal might expect PrismFunctionParameter directly from prism-ts,
34
+ // or we transform them before passing. Let's assume it takes PrismFunctionParameter for now or RLFunctionParameter.
35
+ // The RLFunctionMetadata in RLSchemaData contains RLFunctionParameter.
36
+ let modalTargetFunctionParams = $state<PrismFunctionParameter[] | null>(null); // Or RLFunctionParameter[]
39
37
  let modalInitialId = $state<string | number | null>(null);
40
38
  let modalInitialDataForPut = $state<Record<string, any>>({});
41
39
 
@@ -43,35 +41,36 @@
43
41
  let showEnumModal = $state(false);
44
42
  let currentEnumDetails = $state<{ name: string; values: string[]; schema: string } | null>(null);
45
43
 
46
- // --- Computation function for currentSelectedSchemaObject ---
47
- function computeCurrentSelectedSchema(): SchemaMetadata | null {
44
+
45
+ function computeCurrentSelectedSchema(): RLSchemaData | null { // Use RLSchemaData
48
46
  const activeName = explorerStore.activeSchemaName;
49
- const currentSchemas = schemas; // Access the $state variable's value
47
+ const currentSchemas = schemas;
50
48
  if (!activeName || !currentSchemas) {
51
49
  return null;
52
50
  }
53
51
  return currentSchemas.find(s => s.name === activeName) || null;
54
52
  }
55
- // Use the computation function with $derived
56
53
  let currentSelectedSchemaObject = $derived(computeCurrentSelectedSchema());
57
54
 
58
- // --- Computation function for currentEntityItems ---
59
55
  function computeCurrentEntityItems(): Record<string, any> | null {
60
- const schemaObj = currentSelectedSchemaObject; // Access the previously derived value
56
+ const schemaObj = currentSelectedSchemaObject;
61
57
  const activeType = explorerStore.activeEntityType;
62
- if (schemaObj && activeType && schemaObj[activeType]) {
63
- const entities = schemaObj[activeType];
64
- return (entities && Object.keys(entities).length > 0) ? entities as Record<string, any> : null;
58
+
59
+ // Ensure explorerStore.activeEntityType is a valid key for RLSchemaData
60
+ if (schemaObj && activeType && schemaObj[activeType as keyof RLSchemaData]) {
61
+ const entities = schemaObj[activeType as keyof RLSchemaData]; // Type assertion
62
+ // Check if entities is not undefined and is an object
63
+ if (entities && typeof entities === 'object' && !Array.isArray(entities)) {
64
+ return (Object.keys(entities).length > 0) ? entities as Record<string, any> : null;
65
+ }
65
66
  }
66
67
  return null;
67
68
  }
68
- // Use the computation function with $derived
69
69
  let currentEntityItems = $derived(computeCurrentEntityItems());
70
70
 
71
71
 
72
- // --- Effects ---
73
72
  $effect(() => {
74
- // This effect now reads the derived values directly
73
+ // ... (scroll effect remains the same, uses derived values) ...
75
74
  const schemaObj = currentSelectedSchemaObject;
76
75
  const focusedName = explorerStore.focusedEntityName;
77
76
  const activeType = explorerStore.activeEntityType;
@@ -100,63 +99,11 @@
100
99
  try {
101
100
  isLoading = true;
102
101
  error = null;
103
- const rawFetchedSchemasFromPrism = await apiStore.prism.getSchemas();
104
- const rawFetchedSchemas = rawFetchedSchemasFromPrism as any[]; // Assuming transformation is still needed
105
-
106
- const transformColumn = (apiCol: any): ColumnMetadata => ({
107
- name: apiCol.name,
108
- type: apiCol.type,
109
- nullable: apiCol.nullable === true,
110
- isPrimaryKey: apiCol.is_pk === true || apiCol.isPrimaryKey === true, // Handles both potential key names
111
- isEnum: apiCol.is_enum === true,
112
- references: apiCol.references ? {
113
- schema: apiCol.references.schema_name || apiCol.references.schema,
114
- table: apiCol.references.table,
115
- column: apiCol.references.column,
116
- } : undefined,
117
- });
102
+ // Fetch raw schemas using prism-ts types
103
+ const rawFetchedSchemasFromPrism = await apiStore.prism.getSchemas() as PrismSchemaMetadata[];
118
104
 
119
- const transformTableOrView = (apiEntity: any, schemaName: string): TableMetadata | ViewMetadata => ({
120
- name: apiEntity.name,
121
- schema: apiEntity.schema_name || apiEntity.schema || schemaName,
122
- columns: (apiEntity.columns || []).map(transformColumn),
123
- });
124
-
125
- const transformEnum = (apiEnum: any, schemaName: string): EnumMetadata => ({
126
- name: apiEnum.name,
127
- schema: apiEnum.schema_name || apiEnum.schema || schemaName,
128
- values: apiEnum.values || [],
129
- });
130
-
131
- const transformFnParam = (param: any): FunctionParameter => ({
132
- name: param.name,
133
- type: param.type,
134
- mode: param.mode || 'IN',
135
- hasDefault: param.has_default === true,
136
- defaultValue: param.default_value === undefined ? null : String(param.default_value),
137
- });
138
-
139
- const transformFunctionLike = (fn: any, schemaName: string): FunctionMetadata => ({
140
- name: fn.name,
141
- schema: fn.schema_name || fn.schema || schemaName,
142
- type: String(fn.type),
143
- objectType: String(fn.object_type),
144
- description: fn.description === undefined ? null : fn.description,
145
- parameters: (fn.parameters || []).map(transformFnParam),
146
- returnType: fn.return_type === undefined ? null : fn.return_type,
147
- isStrict: fn.is_strict === true,
148
- ...(fn.object_type && String(fn.object_type).includes('TRIGGER') && fn.trigger_data ? { triggerData: fn.trigger_data } : {})
149
- });
150
-
151
- const transformedSchemasResult: SchemaMetadata[] = rawFetchedSchemas.map((apiSchema: any) => ({
152
- name: apiSchema.name,
153
- tables: Object.fromEntries(Object.entries(apiSchema.tables || {}).map(([k, v]) => [k, transformTableOrView(v, apiSchema.name) as TableMetadata])),
154
- views: Object.fromEntries(Object.entries(apiSchema.views || {}).map(([k, v]) => [k, transformTableOrView(v, apiSchema.name) as ViewMetadata])),
155
- enums: Object.fromEntries(Object.entries(apiSchema.enums || {}).map(([k, v]) => [k, transformEnum(v, apiSchema.name)])),
156
- functions: Object.fromEntries(Object.entries(apiSchema.functions || {}).map(([k, v]) => [k, transformFunctionLike(v, apiSchema.name)])),
157
- procedures: Object.fromEntries(Object.entries(apiSchema.procedures || {}).map(([k, v]) => [k, transformFunctionLike(v, apiSchema.name)])),
158
- triggers: Object.fromEntries(Object.entries(apiSchema.triggers || {}).map(([k, v]) => [k, transformFunctionLike(v, apiSchema.name)])),
159
- }));
105
+ // Use the new transformer
106
+ const transformedSchemasResult = transformPrismSchemasToRLData(rawFetchedSchemasFromPrism);
160
107
 
161
108
  schemas = transformedSchemasResult;
162
109
 
@@ -177,26 +124,37 @@
177
124
  loadAndTransformSchemas();
178
125
  });
179
126
 
180
- // --- Helper Functions ---
181
- function getEntityCount(schema: SchemaMetadata | null, entityType: StoreExplorerEntityType): number {
182
- if (!schema || !schema[entityType]) return 0;
183
- const entities = schema[entityType];
184
- return entities ? Object.keys(entities).length : 0;
127
+ function getEntityCount(schema: RLSchemaData | null, entityType: StoreExplorerEntityType): number {
128
+ if (!schema || !schema[entityType as keyof RLSchemaData]) return 0;
129
+ const entities = schema[entityType as keyof RLSchemaData];
130
+ return (entities && typeof entities === 'object' && !Array.isArray(entities)) ? Object.keys(entities).length : 0;
185
131
  }
186
-
187
- function getColumnsForTableOrView(item: TableMetadata | ViewMetadata): ColumnMetadata[] {
132
+
133
+ // This function should now expect RLTableMetadata or RLViewMetadata and return RLColumnMetadata[]
134
+ function getColumnsForTableOrView(item: RLTableMetadata | RLViewMetadata): RLColumnMetadata[] {
188
135
  return item.columns || [];
189
136
  }
190
137
 
191
- // --- Modal Control Functions ---
192
- function openModalForApi( // Renamed to openModalForApi
138
+ function openModalForApi(
193
139
  params: { operation: APIOperation },
194
140
  currentSchemaFromCall: string,
195
141
  currentResourceName: string,
196
142
  currentModalResourceType: ModalResourceType,
197
143
  options: {
198
- columns?: ColumnMetadata[];
199
- functionParams?: FunctionMetadata['parameters'] | null;
144
+ // Expect RLColumnMetadata for consistency if RLApiOperationModal is adapted
145
+ columns?: RLColumnMetadata[];
146
+ // For function parameters, `itemData as RLFunctionMetadata` will have `parameters` of type RLFunctionParameter[]
147
+ // The RLApiOperationModal props expect PrismFunctionParameter[] (or null).
148
+ // We need to either adapt RLApiOperationModal or transform back if strictly necessary.
149
+ // For simplicity, let's assume RLApiOperationModal can be adapted or we can transform `RLFunctionParameter[]`
150
+ // back to `PrismFunctionParameter[]` if RLApiOperationModal MUST take the prism-ts type.
151
+ // If RLApiOperationModal is changed to use RLFunctionParameter, then no conversion needed.
152
+ // If it stays with PrismFunctionParameter, then:
153
+ // functionParams?: PrismFunctionParameter[] | null;
154
+ // And when calling:
155
+ // functionParams: (itemData as RLFunctionMetadata).parameters.map(transformRLParamToPrismParam)
156
+ // Let's keep modalTargetFunctionParams as PrismFunctionParameter for now as it was
157
+ functionParams?: PrismFunctionParameter[] | null; // Sticking to what modal currently expects from props
200
158
  initialId?: string | number | null;
201
159
  initialDataForPut?: Record<string, any>;
202
160
  } = {}
@@ -214,28 +172,73 @@
214
172
 
215
173
  function closeModal() {
216
174
  isModalOpen = false;
217
- modalTargetSchemaName = '';
218
- modalTargetResourceName = '';
219
- modalTargetColumns = [];
220
- modalTargetFunctionParams = null;
221
- modalInitialId = null;
222
- modalInitialDataForPut = {};
175
+ // ... (reset modal state properties) ...
223
176
  }
224
177
 
225
- // --- Event Handlers / Callbacks from Child Components ---
226
- function handleFkReferenceClicked(ref: ColumnReference) {
178
+ function handleFkReferenceClicked(ref: RLColumnReference) { // Expects RLColumnReference
227
179
  explorerStore.navigateToEntity(ref.schema, 'tables', ref.table);
228
180
  }
229
181
 
230
- function handleEnumBadgeClicked(enumData: { name: string; values: string[]; schema: string }) {
231
- currentEnumDetails = enumData; // { name, values, schema }
182
+ // RLEnumMetadata provides name, values, schema directly
183
+ function handleEnumBadgeClicked(enumData: RLEnumMetadata) {
184
+ currentEnumDetails = { name: enumData.name, values: enumData.values, schema: enumData.schema };
232
185
  showEnumModal = true;
233
186
  }
234
187
  </script>
235
188
 
236
189
  <!-- Template for src/lib/components/explorer/RLSchemaExplorer.svelte -->
237
- <!-- This should be placed directly below the </script> tag from the previous response -->
190
+ <!-- ... (Template remains largely the same, but ensure data bindings use RL... types) ... -->
191
+ <!-- Example changes in template: -->
192
+ <!-- For RLMetadataTable:
193
+ columns={getColumnsForTableOrView(typedItem as (RLTableMetadata | RLViewMetadata))}
194
+ enumsInSchema={activeSchema.enums as Record<string, RLEnumMetadata>} // Cast might be needed
195
+ onEnumClick={(enumData) => handleEnumBadgeClicked(enumData)} // This will now pass RLEnumMetadata
196
+ -->
197
+ <!-- For RLApiInterface call:
198
+ columns={getColumnsForTableOrView(typedItem as (RLTableMetadata | RLViewMetadata))}
199
+ onOpenModal={(opParams) => openModalForApi(
200
+ opParams,
201
+ activeSchema.name,
202
+ name,
203
+ explorerStore.activeEntityType === 'tables' ? 'table' : 'view',
204
+ // Pass RLColumnMetadata
205
+ { columns: getColumnsForTableOrView(typedItem as (RLTableMetadata | RLViewMetadata)) }
206
+ )}
207
+ -->
208
+ <!-- For Function/Procedure RLApiInterface call:
209
+ onOpenModal={(opParams) => {
210
+ const funcMeta = typedItem as RLFunctionMetadata;
211
+ // IMPORTANT: Here's where a decision for functionParams is needed.
212
+ // If RLApiOperationModal expects PrismFunctionParameter[]:
213
+ // You would need a function `transformRLParamToPrismParam` or map directly here.
214
+ // For now, assuming RLFunctionMetadata.parameters IS what RLApiOperationModal's functionParams prop wants (which is PrismFunctionParameter[]).
215
+ // This implies that our RLFunctionParameter and PrismFunctionParameter are structurally compatible or RLApiOperationModal is adapted.
216
+ // If RLFunctionParameter IS different from PrismFunctionParameter in a way that matters to the modal:
217
+ // const prismParams: PrismFunctionParameter[] = funcMeta.parameters.map(rlParam => ({...rlParam, defaultValue: rlParam.defaultValue ?? undefined }));
218
+ // The current RLFunctionParameter in types/explorer.ts matches PrismFunctionParameter structure sufficiently for the RLApiOperationModal to consume if `functionParams` prop remains PrismFunctionParameter[]
219
+
220
+ openModalForApi(
221
+ opParams,
222
+ activeSchema.name,
223
+ name,
224
+ 'function',
225
+ // Assuming RLApiOperationModal expects PrismFunctionParameter[] and our RLFunctionParameter[] from typedItem is compatible.
226
+ // If types diverge significantly, a mapping function would be needed here.
227
+ // The `modalTargetFunctionParams = $state<PrismFunctionParameter[] | null>(null);` means we have to ensure the structure is compatible or map it.
228
+ // Our RLFunctionParameter looks very similar to PrismFunctionParameter in the example `RLApiOperationModal`.
229
+ { functionParams: (typedItem as RLFunctionMetadata).parameters as unknown as PrismFunctionParameter[] } // Cast if shapes are compatible
230
+ );
231
+ }}
232
+ -->
233
+ <!-- In the #each Object.entries(currentEntityItems) -->
234
+ <!-- Change itemData casts: -->
235
+ <!-- {@const typedItem = itemData as (RLTableMetadata | RLViewMetadata)} -->
236
+ <!-- {@const typedItem = itemData as RLEnumMetadata} -->
237
+ <!-- {@const typedItem = itemData as RLFunctionMetadata} -->
238
238
 
239
+ <!-- RLApiOperationModal call needs to match prop types. If RLApiOperationModal's `columns` prop changes to RLColumnMetadata[], then no cast is needed.
240
+ Otherwise, `modalTargetColumns as unknown as PrismColumnMetadata[]` might be needed.
241
+ Similar for `functionParams`. -->
239
242
  <div class="container mx-auto p-4">
240
243
  {#if isLoading}
241
244
  <div class="flex flex-col items-center justify-center h-64">
@@ -298,7 +301,7 @@
298
301
  {@const entityId = `entity-${activeSchema.name}-${explorerStore.activeEntityType}-${name}`}
299
302
  <!-- Display for Tables and Views -->
300
303
  {#if explorerStore.activeEntityType === 'tables' || explorerStore.activeEntityType === 'views'}
301
- {@const typedItem = itemData as (TableMetadata | ViewMetadata)}
304
+ {@const typedItem = itemData as (RLTableMetadata | RLViewMetadata)}
302
305
  <div class="collapse collapse-arrow bg-base-100 shadow-md {explorerStore.focusedEntityName === name ? 'collapse-open ring-1 ring-primary ring-offset-base-100 ring-offset-2' : ''}" id={entityId}>
303
306
  <input
304
307
  type="checkbox"
@@ -317,16 +320,22 @@
317
320
  onkeydown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); explorerStore.focusOnEntity(explorerStore.focusedEntityName === name ? null : name);}}}
318
321
  >
319
322
  {name}
320
- <span class="badge badge-ghost ml-2 capitalize">{explorerStore.activeEntityType.slice(0, -1)}</span>
323
+ <span class="badge badge-ghost ml-2 capitalize">{explorerStore.activeEntityType === 'tables' ? 'Table' : 'View'}</span>
321
324
  </div>
322
325
  <div class="collapse-content">
323
326
  <RLMetadataTable
324
327
  title={name}
325
328
  itemType={explorerStore.activeEntityType === 'tables' ? 'table' : 'view'}
326
329
  columns={getColumnsForTableOrView(typedItem)}
327
- enumsInSchema={activeSchema.enums}
330
+ enumsInSchema={activeSchema.enums as Record<string, RLEnumMetadata>}
328
331
  onFkClick={handleFkReferenceClicked}
329
- onEnumClick={(enumData) => handleEnumBadgeClicked({...enumData, schema: activeSchema.name})}
332
+ onEnumClick={(enumDataFromTable) => {
333
+ // RLMetadataTable will pass the RLEnumMetadata for the clicked enum
334
+ // (or details if `onEnumClick` prop in RLMetadataTable is updated to send just `name`, `values`)
335
+ // Assuming it passes the full RLEnumMetadata or an object with {name, values} and schema is added here
336
+ const foundEnum = activeSchema.enums[enumDataFromTable.name]; // Or how enumDataFromTable is structured
337
+ if(foundEnum) handleEnumBadgeClicked(foundEnum);
338
+ }}
330
339
  />
331
340
  <div class="mt-4 p-2 border-t border-base-300">
332
341
  <RLApiInterface
@@ -347,7 +356,7 @@
347
356
  </div>
348
357
  <!-- Display for Enums -->
349
358
  {:else if explorerStore.activeEntityType === 'enums'}
350
- {@const typedItem = itemData as EnumMetadata}
359
+ {@const typedItem = itemData as RLEnumMetadata}
351
360
  <div class="collapse collapse-arrow bg-base-100 shadow-md" id={entityId}>
352
361
  <input type="checkbox" name="item-accordion-{activeSchema.name}-enum-{name}" />
353
362
  <div
@@ -366,14 +375,14 @@
366
375
  <li>{value}</li>
367
376
  {/each}
368
377
  </ul>
369
- <button class="btn btn-xs btn-outline mt-2" onclick={() => handleEnumBadgeClicked({ name: typedItem.name, values: typedItem.values, schema: activeSchema.name })}>
378
+ <button class="btn btn-xs btn-outline mt-2" onclick={() => handleEnumBadgeClicked(typedItem)}>
370
379
  Show Details
371
380
  </button>
372
381
  </div>
373
382
  </div>
374
- <!-- Display for Functions, Procedures, Triggers -->
383
+ <!-- Display for Functions, Procedures, Triggers (now consolidated under RLFunctionMetadata) -->
375
384
  {:else if explorerStore.activeEntityType === 'functions' || explorerStore.activeEntityType === 'procedures' || explorerStore.activeEntityType === 'triggers'}
376
- {@const typedItem = itemData as FunctionMetadata}
385
+ {@const typedItem = itemData as RLFunctionMetadata}
377
386
  <div class="collapse collapse-arrow bg-base-100 shadow-md" id={entityId}>
378
387
  <input type="checkbox" name="item-accordion-{activeSchema.name}-{explorerStore.activeEntityType}-{name}" />
379
388
  <div
@@ -383,7 +392,8 @@
383
392
  onclick={(e) => { const input = e.currentTarget.previousElementSibling as HTMLInputElement; if(input) input.checked = !input.checked;}}
384
393
  onkeydown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); const input = e.currentTarget.previousElementSibling as HTMLInputElement; if(input) input.checked = !input.checked;}}}
385
394
  >
386
- {name} <span class="badge badge-ghost ml-2 capitalize">{(typedItem.objectType || explorerStore.activeEntityType).replace('ObjectType.', '').replace('FunctionType.','').toLowerCase()}</span>
395
+ <!-- Display 'kind' which now includes PROCEDURE, TRIGGER etc. -->
396
+ {name} <span class="badge badge-ghost ml-2 capitalize">{typedItem.kind.toLowerCase()}</span>
387
397
  </div>
388
398
  <div class="collapse-content">
389
399
  {#if typedItem.description}
@@ -411,7 +421,7 @@
411
421
  <p class="text-sm opacity-70 mt-2">No parameters.</p>
412
422
  {/if}
413
423
 
414
- {#if explorerStore.activeEntityType === 'functions' || explorerStore.activeEntityType === 'procedures'}
424
+ {#if typedItem.kind === 'FUNCTION' || typedItem.kind === 'PROCEDURE'}
415
425
  <div class="mt-4 p-2 border-t border-base-300">
416
426
  <RLApiInterface
417
427
  schemaName={activeSchema.name}
@@ -423,7 +433,9 @@
423
433
  activeSchema.name,
424
434
  name,
425
435
  'function',
426
- { functionParams: typedItem.parameters }
436
+ // typedItem.parameters are RLFunctionParameter[]. RLApiOperationModal expects PrismFunctionParameter[]
437
+ // Assuming direct castability for now due to similar structure
438
+ { functionParams: typedItem.parameters as unknown as PrismFunctionParameter[] }
427
439
  )}
428
440
  />
429
441
  </div>
@@ -459,8 +471,8 @@
459
471
  onClose={closeModal}
460
472
  schemaName={modalTargetSchemaName}
461
473
  resourceName={modalTargetResourceName}
462
- operation={modalTargetOperation}
463
- columns={modalTargetColumns}
474
+ operation={modalTargetOperation}
475
+ columns={modalTargetColumns as unknown as PrismColumnMetadata[]}
464
476
  functionParams={modalTargetFunctionParams}
465
477
  resourceType={modalTargetResourceType}
466
478
  initialId={modalInitialId}
@@ -1,19 +1,7 @@
1
1
  <!-- src/lib/components/forms/RLFilterForm.svelte -->
2
2
  <script lang="ts">
3
3
  import type { ColumnMetadata } from '@yrrrrrf/prism-ts';
4
-
5
- // Same helper as in RLResourceForm, can be moved to a shared utility file
6
- function mapSqlTypeToInputType(sqlType: string): string {
7
- const lowerSqlType = sqlType.toLowerCase();
8
- if (lowerSqlType.includes('bool')) return 'checkbox'; // Or a select with true/false/any
9
- if (lowerSqlType.includes('int') || lowerSqlType.includes('serial') || lowerSqlType.includes('numeric') || lowerSqlType.includes('decimal') || lowerSqlType.includes('real') || lowerSqlType.includes('double')) return 'number';
10
- if (lowerSqlType.includes('date') && !lowerSqlType.includes('timestamp')) return 'date';
11
- if (lowerSqlType.includes('timestamp')) return 'datetime-local';
12
- if (lowerSqlType.includes('time') && !lowerSqlType.includes('timestamp')) return 'time';
13
- // For filters, textarea is less common unless for full-text search
14
- return 'text'; // Default for filters
15
- }
16
-
4
+ import { mapSqlTypeToInputType } from '../../tools/form-helpers.js';
17
5
 
18
6
  let {
19
7
  columns,
@@ -1,19 +1,8 @@
1
1
  <!-- src/lib/components/forms/RLFunctionForm.svelte -->
2
2
  <script lang="ts">
3
3
  import type { FunctionParameter } from '@yrrrrrf/prism-ts';
4
-
5
- // Helper, can be shared
6
- function mapSqlTypeToInputType(sqlType: string): string {
7
- const lowerSqlType = sqlType.toLowerCase();
8
- if (lowerSqlType.includes('bool')) return 'checkbox';
9
- if (lowerSqlType.includes('int') || lowerSqlType.includes('serial') || lowerSqlType.includes('numeric')) return 'number';
10
- if (lowerSqlType.includes('date') && !lowerSqlType.includes('timestamp')) return 'date';
11
- if (lowerSqlType.includes('timestamp')) return 'datetime-local';
12
- if (lowerSqlType.includes('time') && !lowerSqlType.includes('timestamp')) return 'time';
13
- if (lowerSqlType.includes('text')) return 'textarea';
14
- if (lowerSqlType.includes('json')) return 'textarea';
15
- return 'text';
16
- }
4
+ import { mapSqlTypeToInputType } from '../../tools/form-helpers.js';
5
+
17
6
 
18
7
  let {
19
8
  params, // FunctionParameter[]
@@ -1,20 +1,8 @@
1
1
  <!-- src/lib/components/forms/RLResourceForm.svelte -->
2
2
  <script lang="ts">
3
3
  import type { ColumnMetadata } from '@yrrrrrf/prism-ts';
4
+ import { mapSqlTypeToInputType } from '../../tools/form-helpers.js';
4
5
 
5
- // Helper to map SQL types to HTML input types
6
- function mapSqlTypeToInputType(sqlType: string, columnName: string): string {
7
- const lowerSqlType = sqlType.toLowerCase();
8
- if (lowerSqlType.includes('bool')) return 'checkbox';
9
- if (lowerSqlType.includes('int') || lowerSqlType.includes('serial') || lowerSqlType.includes('numeric') || lowerSqlType.includes('decimal') || lowerSqlType.includes('real') || lowerSqlType.includes('double')) return 'number';
10
- if (lowerSqlType.includes('date') && !lowerSqlType.includes('timestamp')) return 'date';
11
- if (lowerSqlType.includes('timestamp')) return 'datetime-local';
12
- if (lowerSqlType.includes('time') && !lowerSqlType.includes('timestamp')) return 'time';
13
- if (lowerSqlType.includes('text') || columnName.includes('description')) return 'textarea'; // Heuristic for textarea
14
- if (lowerSqlType.includes('json')) return 'textarea'; // JSON often edited as text
15
- // Add more mappings as needed (e.g., for enums to select, bytea to file (later))
16
- return 'text'; // Default
17
- }
18
6
 
19
7
  let {
20
8
  columns,
@@ -23,4 +23,60 @@ declare class ExplorerStore {
23
23
  reset(): void;
24
24
  }
25
25
  export declare const explorerStore: ExplorerStore;
26
+ export interface RLColumnReference {
27
+ schema: string;
28
+ table: string;
29
+ column: string;
30
+ }
31
+ export interface RLColumnMetadata {
32
+ name: string;
33
+ type: string;
34
+ nullable: boolean;
35
+ isPrimaryKey: boolean;
36
+ isEnum: boolean;
37
+ references?: RLColumnReference;
38
+ }
39
+ export interface RLBaseEntityMetadata {
40
+ name: string;
41
+ schema: string;
42
+ }
43
+ export interface RLTableMetadata extends RLBaseEntityMetadata {
44
+ columns: RLColumnMetadata[];
45
+ }
46
+ export interface RLViewMetadata extends RLBaseEntityMetadata {
47
+ columns: RLColumnMetadata[];
48
+ }
49
+ export interface RLEnumMetadata extends RLBaseEntityMetadata {
50
+ values: string[];
51
+ }
52
+ export interface RLFunctionParameter {
53
+ name: string;
54
+ type: string;
55
+ mode: "IN" | "OUT" | "INOUT" | "VARIADIC";
56
+ hasDefault: boolean;
57
+ defaultValue?: string | null;
58
+ }
59
+ export type RLFunctionKind = "SCALAR" | "TABLE" | "SET_RETURNING" | "AGGREGATE" | "WINDOW" | "PROCEDURE" | "TRIGGER" | "FUNCTION" | "UNKNOWN";
60
+ export interface RLFunctionMetadata extends RLBaseEntityMetadata {
61
+ kind: RLFunctionKind;
62
+ description?: string | null;
63
+ parameters: RLFunctionParameter[];
64
+ returnType?: string | null;
65
+ isStrict: boolean;
66
+ triggerData?: {
67
+ timing: string;
68
+ events: string[];
69
+ targetTableSchema: string;
70
+ targetTableName: string;
71
+ };
72
+ }
73
+ export interface RLSchemaData {
74
+ name: string;
75
+ tables: Record<string, RLTableMetadata>;
76
+ views: Record<string, RLViewMetadata>;
77
+ enums: Record<string, RLEnumMetadata>;
78
+ functions: Record<string, RLFunctionMetadata>;
79
+ procedures: Record<string, RLFunctionMetadata>;
80
+ triggers: Record<string, RLFunctionMetadata>;
81
+ }
26
82
  export {};
package/dist/mod.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  export { apiStore } from "./components/stores/api.svelte.ts";
2
2
  export { appData } from "./components/stores/app.svelte.ts";
3
+ export * from "./tools/schema-transformer.ts";
4
+ export * from "./tools/form-helpers.ts";
3
5
  export declare function getVersion(): string;
4
6
  export declare function init(): void;
package/dist/mod.js CHANGED
@@ -5,6 +5,8 @@
5
5
  export { apiStore } from "./components/stores/api.svelte.ts";
6
6
  export { appData } from "./components/stores/app.svelte.ts";
7
7
  // export { type AppData, appData } from "./stores/app.svelte.ts";
8
+ export * from "./tools/schema-transformer.ts";
9
+ export * from "./tools/form-helpers.ts";
8
10
  // // * Import the main forge from the ts-forge library...
9
11
  // export {
10
12
  // type FooterConfig,
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Maps SQL data types to HTML input element types.
3
+ * @param sqlType The SQL data type string.
4
+ * @param columnName (Optional) The name of the column, can be used for heuristics.
5
+ * @returns The corresponding HTML input type string.
6
+ */
7
+ export declare function mapSqlTypeToInputType(sqlType: string, columnName?: string): string;
8
+ /**
9
+ * Determines the 'step' attribute for number inputs based on SQL type.
10
+ * @param sqlType The SQL data type string.
11
+ * @returns 'any' for floating point types, undefined otherwise.
12
+ */
13
+ export declare function getStepForNumberInput(sqlType: string): string | undefined;
14
+ /**
15
+ * Formats a column reference object into a display string.
16
+ * @param ref The column reference object.
17
+ * @returns A string representation like "schema.table.column".
18
+ */
19
+ export declare function formatReferenceText(ref: {
20
+ schema: string;
21
+ table: string;
22
+ column: string;
23
+ } | undefined): string;
@@ -0,0 +1,51 @@
1
+ // File: src/lib/tools/form-helpers.ts
2
+ /**
3
+ * Maps SQL data types to HTML input element types.
4
+ * @param sqlType The SQL data type string.
5
+ * @param columnName (Optional) The name of the column, can be used for heuristics.
6
+ * @returns The corresponding HTML input type string.
7
+ */
8
+ export function mapSqlTypeToInputType(sqlType, columnName) {
9
+ const lowerSqlType = sqlType.toLowerCase();
10
+ if (lowerSqlType.includes("bool"))
11
+ return "checkbox";
12
+ if (lowerSqlType.includes("int") || lowerSqlType.includes("serial") ||
13
+ lowerSqlType.includes("numeric") || lowerSqlType.includes("decimal") ||
14
+ lowerSqlType.includes("real") || lowerSqlType.includes("double"))
15
+ return "number";
16
+ if (lowerSqlType.includes("date") && !lowerSqlType.includes("timestamp"))
17
+ return "date";
18
+ if (lowerSqlType.includes("timestamp"))
19
+ return "datetime-local";
20
+ if (lowerSqlType.includes("time") && !lowerSqlType.includes("timestamp"))
21
+ return "time";
22
+ if (lowerSqlType.includes("text") ||
23
+ (columnName && columnName.toLowerCase().includes("description")))
24
+ return "textarea";
25
+ if (lowerSqlType.includes("json"))
26
+ return "textarea"; // JSON often best edited as text in simple forms
27
+ return "text"; // Default
28
+ }
29
+ /**
30
+ * Determines the 'step' attribute for number inputs based on SQL type.
31
+ * @param sqlType The SQL data type string.
32
+ * @returns 'any' for floating point types, undefined otherwise.
33
+ */
34
+ export function getStepForNumberInput(sqlType) {
35
+ const lowerSqlType = sqlType.toLowerCase();
36
+ if (lowerSqlType.includes("decimal") || lowerSqlType.includes("numeric") ||
37
+ lowerSqlType.includes("real") || lowerSqlType.includes("double")) {
38
+ return "any";
39
+ }
40
+ return undefined; // Default step (usually 1 for integers)
41
+ }
42
+ /**
43
+ * Formats a column reference object into a display string.
44
+ * @param ref The column reference object.
45
+ * @returns A string representation like "schema.table.column".
46
+ */
47
+ export function formatReferenceText(ref) {
48
+ if (!ref)
49
+ return "";
50
+ return `${ref.schema}.${ref.table}.${ref.column}`;
51
+ }
@@ -0,0 +1,5 @@
1
+ import type { ColumnMetadata as PrismColumnMetadata, FunctionMetadata as PrismFunctionMetadata, SchemaMetadata as PrismSchemaMetadata } from "@yrrrrrf/prism-ts";
2
+ import type { RLColumnMetadata, RLFunctionMetadata, RLSchemaData } from "../components/stores/explorer.svelte.ts";
3
+ export declare function transformPrismColumn(prismCol: PrismColumnMetadata): RLColumnMetadata;
4
+ export declare function transformPrismFunction(prismFn: PrismFunctionMetadata, schema: string): RLFunctionMetadata;
5
+ export declare function transformPrismSchemasToRLData(prismSchemas: PrismSchemaMetadata[]): RLSchemaData[];
@@ -0,0 +1,130 @@
1
+ // File: src/lib/tools/schema-transformer.ts
2
+ // --- Helper Functions ---
3
+ function transformPrismColumnReference(ref) {
4
+ if (!ref)
5
+ return undefined;
6
+ return {
7
+ schema: ref.schema, // prism-ts seems to use 'schema' directly
8
+ table: ref.table,
9
+ column: ref.column,
10
+ };
11
+ }
12
+ export function transformPrismColumn(prismCol) {
13
+ return {
14
+ name: prismCol.name,
15
+ type: prismCol.type,
16
+ nullable: prismCol.nullable,
17
+ // Adapting from prism-ts field `isPrimaryKey` to `isPrimaryKey`
18
+ // And `isEnum` to `isEnum`
19
+ isPrimaryKey: prismCol.isPrimaryKey === true,
20
+ isEnum: prismCol.isEnum === true,
21
+ references: transformPrismColumnReference(prismCol.references),
22
+ };
23
+ }
24
+ function transformPrismTableOrView(prismEntity, schema) {
25
+ return {
26
+ name: prismEntity.name,
27
+ schema: schema, // prism-ts's TableMetadata includes schema
28
+ columns: (prismEntity.columns || []).map(transformPrismColumn),
29
+ };
30
+ }
31
+ function transformPrismEnum(prismEnum, schema) {
32
+ return {
33
+ name: prismEnum.name,
34
+ schema: schema, // prism-ts's EnumMetadata includes schema
35
+ values: prismEnum.values || [],
36
+ };
37
+ }
38
+ function transformPrismFunctionParameter(param) {
39
+ return {
40
+ name: param.name,
41
+ type: param.type,
42
+ mode: param.mode || "IN", // prism-ts uses string
43
+ hasDefault: param.hasDefault === true,
44
+ defaultValue: param.defaultValue === undefined ? null : String(param.defaultValue),
45
+ };
46
+ }
47
+ function determineRLFunctionKind(prismFn) {
48
+ const objectType = (prismFn.objectType || "").toUpperCase();
49
+ const fnType = (prismFn.type || "").toUpperCase(); // e.g., SCALAR, TABLE, SET
50
+ if (objectType === "PROCEDURE")
51
+ return "PROCEDURE";
52
+ if (objectType === "TRIGGER")
53
+ return "TRIGGER";
54
+ if (objectType === "FUNCTION" || objectType === "") { // Default to FUNCTION if objectType is missing
55
+ if (fnType === "SCALAR")
56
+ return "SCALAR";
57
+ if (fnType === "TABLE")
58
+ return "TABLE";
59
+ if (fnType === "SET")
60
+ return "SET_RETURNING"; // prism-ts calls it 'set'
61
+ if (fnType === "AGGREGATE")
62
+ return "AGGREGATE";
63
+ if (fnType === "WINDOW")
64
+ return "WINDOW";
65
+ // todo: Check if prism-ts has other specific function types
66
+ // todo: If prism-ts has a 'FUNCTION' type, we can return it here
67
+ // todo: Check if we need to handle 'FUNCTION' as a generic type...
68
+ return "FUNCTION"; // Generic function if type is not more specific
69
+ // todo: (probably not needed, as we can use 'FUNCTION' for generic functions)
70
+ // todo: (probably not needed, as we can use 'FUNCTION' for generic functions)
71
+ // todo: (probably not needed, as we can use 'FUNCTION' for generic functions)
72
+ }
73
+ return "UNKNOWN";
74
+ }
75
+ export function transformPrismFunction(prismFn, schema) {
76
+ const kind = determineRLFunctionKind(prismFn);
77
+ const transformed = {
78
+ name: prismFn.name,
79
+ schema: schema, // prism-ts's FunctionMetadata includes schema
80
+ kind: kind,
81
+ description: prismFn.description === undefined ? null : prismFn.description,
82
+ parameters: (prismFn.parameters || []).map(transformPrismFunctionParameter),
83
+ returnType: prismFn.returnType === undefined ? null : prismFn.returnType,
84
+ isStrict: prismFn.isStrict === true,
85
+ };
86
+ // Example: If prism-ts provides trigger-specific data on its FunctionMetadata when objectType is 'trigger'
87
+ // We would map it here to transformed.triggerData
88
+ // if (kind === 'TRIGGER' && (prismFn as any).triggerData) {
89
+ // transformed.triggerData = {
90
+ // timing: (prismFn as any).triggerData.timing,
91
+ // events: (prismFn as any).triggerData.events,
92
+ // targetTableSchema: (prismFn as any).triggerData.table_schema,
93
+ // targetTableName: (prismFn as any).triggerData.table_name,
94
+ // };
95
+ // }
96
+ return transformed;
97
+ }
98
+ // --- Main Transformation Function ---
99
+ export function transformPrismSchemasToRLData(prismSchemas) {
100
+ return prismSchemas.map((prismSchema) => {
101
+ const rlSchema = {
102
+ name: prismSchema.name,
103
+ tables: {},
104
+ views: {},
105
+ enums: {},
106
+ functions: {},
107
+ procedures: {},
108
+ triggers: {},
109
+ };
110
+ Object.entries(prismSchema.tables || {}).forEach(([name, table]) => {
111
+ rlSchema.tables[name] = transformPrismTableOrView(table, prismSchema.name);
112
+ });
113
+ Object.entries(prismSchema.views || {}).forEach(([name, view]) => {
114
+ rlSchema.views[name] = transformPrismTableOrView(view, prismSchema.name);
115
+ });
116
+ Object.entries(prismSchema.enums || {}).forEach(([name, enumData]) => {
117
+ rlSchema.enums[name] = transformPrismEnum(enumData, prismSchema.name);
118
+ });
119
+ Object.entries(prismSchema.functions || {}).forEach(([name, func]) => {
120
+ rlSchema.functions[name] = transformPrismFunction(func, prismSchema.name);
121
+ });
122
+ Object.entries(prismSchema.procedures || {}).forEach(([name, proc]) => {
123
+ rlSchema.procedures[name] = transformPrismFunction(proc, prismSchema.name);
124
+ });
125
+ Object.entries(prismSchema.triggers || {}).forEach(([name, trig]) => {
126
+ rlSchema.triggers[name] = transformPrismFunction(trig, prismSchema.name);
127
+ });
128
+ return rlSchema;
129
+ });
130
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rune-lab",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "license": "MIT",
5
5
  "scripts": {
6
6
  "dev": "vite dev",