rune-lab 0.0.6 → 0.0.7

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 (31) hide show
  1. package/README.md +0 -2
  2. package/dist/components/api/RLApiInterface.svelte +52 -0
  3. package/dist/components/api/RLApiInterface.svelte.d.ts +14 -0
  4. package/dist/components/api/RLApiOperationModal.svelte +190 -0
  5. package/dist/components/api/RLApiOperationModal.svelte.d.ts +18 -0
  6. package/dist/components/dataview/RLMetadataTable.svelte +140 -0
  7. package/dist/components/dataview/RLMetadataTable.svelte.d.ts +15 -0
  8. package/dist/components/explorer/RLSchemaExplorer.svelte +494 -0
  9. package/dist/components/explorer/RLSchemaExplorer.svelte.d.ts +3 -0
  10. package/dist/components/form/RLFilterForm.svelte +131 -0
  11. package/dist/components/form/RLFilterForm.svelte.d.ts +15 -0
  12. package/dist/components/form/RLFunctionForm.svelte +95 -0
  13. package/dist/components/form/RLFunctionForm.svelte.d.ts +9 -0
  14. package/dist/components/form/RLResourceForm.svelte +176 -0
  15. package/dist/components/form/RLResourceForm.svelte.d.ts +11 -0
  16. package/dist/components/layout/URLDisplay.svelte +7 -6
  17. package/dist/components/stores/api.svelte.d.ts +34 -17
  18. package/dist/components/stores/api.svelte.js +97 -45
  19. package/dist/components/stores/app.svelte.d.ts +15 -0
  20. package/dist/components/stores/app.svelte.js +24 -0
  21. package/dist/components/stores/explorer.svelte.d.ts +26 -0
  22. package/dist/components/stores/explorer.svelte.js +113 -0
  23. package/dist/components/ui/showcase/BasicUI.svelte +1 -1
  24. package/dist/components/ui/showcase/main.svelte +1 -1
  25. package/dist/mod.d.ts +2 -0
  26. package/dist/mod.js +3 -2
  27. package/dist/tools/format.d.ts +20 -0
  28. package/dist/tools/format.js +25 -0
  29. package/dist/tools/pdf.d.ts +1 -0
  30. package/dist/tools/pdf.js +320 -0
  31. package/package.json +14 -12
package/README.md CHANGED
@@ -1,5 +1,3 @@
1
- # <div align="center">
2
-
3
1
  <h1 align="center">
4
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">
5
3
  <div align="center">Rune Lab</div>
@@ -0,0 +1,52 @@
1
+ <!-- src/lib/components/api/RLApiInterface.svelte -->
2
+ <script lang="ts">
3
+ import type { ColumnMetadata } from '@yrrrrrf/prism-ts';
4
+ type APIOperation = 'GET' | 'POST' | 'PUT' | 'DELETE';
5
+
6
+ let {
7
+ schemaName,
8
+ resourceName,
9
+ resourceType,
10
+ columns,
11
+ onOpenModal // <-- ADDED THIS PROP
12
+ } = $props<{
13
+ schemaName: string;
14
+ resourceName: string;
15
+ resourceType: 'table' | 'view' | 'function'; // Added 'function' for future
16
+ columns: ColumnMetadata[]; // Still relevant for tables/views context
17
+ onOpenModal: (params: { operation: APIOperation }) => void; // <-- TYPE FOR THE PROP
18
+ }>();
19
+
20
+ function getAllowedOps(type: 'table' | 'view' | 'function'): APIOperation[] {
21
+ if (type === 'table') return ['GET', 'POST', 'PUT', 'DELETE'];
22
+ if (type === 'view') return ['GET'];
23
+ if (type === 'function') return ['POST']; // Typical for functions
24
+ return [];
25
+ }
26
+
27
+ const operationDetails: Record<APIOperation, { label: string, class: string }> = {
28
+ GET: { label: 'GET', class: 'btn-info' },
29
+ POST: { label: 'POST', class: 'btn-success' },
30
+ PUT: { label: 'PUT', class: 'btn-warning' },
31
+ DELETE: { label: 'DELETE', class: 'btn-error' },
32
+ };
33
+
34
+ function handleOperationClick(operation: APIOperation) {
35
+ onOpenModal({ operation });
36
+ }
37
+
38
+ </script>
39
+
40
+ <div class="flex flex-wrap gap-2 my-2">
41
+ {#each getAllowedOps(resourceType) as operation}
42
+ {@const detail = operationDetails[operation]}
43
+ {#if detail} <!-- Added a check in case an operation is not in details -->
44
+ <button
45
+ class="btn btn-sm {detail.class}"
46
+ onclick={() => handleOperationClick(operation)}
47
+ >
48
+ {detail.label}
49
+ </button>
50
+ {/if}
51
+ {/each}
52
+ </div>
@@ -0,0 +1,14 @@
1
+ import type { ColumnMetadata } from '@yrrrrrf/prism-ts';
2
+ type APIOperation = 'GET' | 'POST' | 'PUT' | 'DELETE';
3
+ type $$ComponentProps = {
4
+ schemaName: string;
5
+ resourceName: string;
6
+ resourceType: 'table' | 'view' | 'function';
7
+ columns: ColumnMetadata[];
8
+ onOpenModal: (params: {
9
+ operation: APIOperation;
10
+ }) => void;
11
+ };
12
+ declare const RlApiInterface: import("svelte").Component<$$ComponentProps, {}, "">;
13
+ type RlApiInterface = ReturnType<typeof RlApiInterface>;
14
+ export default RlApiInterface;
@@ -0,0 +1,190 @@
1
+ <!-- src/lib/components/api/RLApiOperationModal.svelte -->
2
+ <script lang="ts">
3
+ import type { ColumnMetadata, FunctionMetadata as PrismFunctionMetadata, CrudOperations } from '@yrrrrrf/prism-ts';
4
+ import { apiStore } from '../stores/api.svelte';
5
+ import RLResourceForm from '../form/RLResourceForm.svelte';
6
+ import RLFilterForm from '../form/RLFilterForm.svelte';
7
+ import RLFunctionForm from '../form/RLFunctionForm.svelte';
8
+
9
+ type APIOperation = 'GET' | 'POST' | 'PUT' | 'DELETE';
10
+ type ModalResourceType = 'table' | 'view' | 'function'; // Specific for modal context
11
+
12
+ let {
13
+ isOpen,
14
+ onClose,
15
+ schemaName,
16
+ resourceName,
17
+ operation,
18
+ columns = [], // Default to empty array if not a table/view
19
+ functionParams = null, // <-- ACCEPT THIS PROP
20
+ resourceType,
21
+ initialId = null,
22
+ initialDataForPut = {}
23
+ } = $props<{
24
+ isOpen: boolean;
25
+ onClose: () => void;
26
+ schemaName: string;
27
+ resourceName: string;
28
+ operation: APIOperation;
29
+ columns?: ColumnMetadata[]; // Optional as functions won't have it
30
+ functionParams?: PrismFunctionMetadata['parameters'] | null; // <-- TYPE FOR PROP
31
+ resourceType: ModalResourceType;
32
+ initialId?: string | number | null;
33
+ initialDataForPut?: Record<string, any>;
34
+ }>();
35
+
36
+ // ... (rest of your existing script block for RLApiOperationModal) ...
37
+ // No structural changes needed in the rest of the script for this specific error,
38
+ // as the logic for handling resourceType === 'function' and using functionParams
39
+ // was already present. The issue was just the prop declaration.
40
+ let loading = $state(false);
41
+ let error = $state<string | null>(null);
42
+ let apiResponse = $state<any>(null);
43
+ let crudOps = $state<CrudOperations<any> | null>(null);
44
+ let recordIdForAction = $state<string | number | undefined>(initialId ?? undefined);
45
+
46
+ let combinedInitialDataForPut = $derived({
47
+ ...initialDataForPut,
48
+ ...(recordIdForAction && columns && columns.find((c: ColumnMetadata) => c.isPrimaryKey) ? { [columns.find((c: ColumnMetadata) => c.isPrimaryKey)!.name]: recordIdForAction } : {})
49
+ });
50
+
51
+ $effect(() => {
52
+ async function initClient() {
53
+ if (isOpen && apiStore.IS_CONNECTED && apiStore.prism && schemaName && resourceName) {
54
+ if (resourceType === 'table' || resourceType === 'view') {
55
+ crudOps = await apiStore.prism.getTableOperations(schemaName, resourceName);
56
+ } else {
57
+ crudOps = null;
58
+ }
59
+ } else {
60
+ crudOps = null;
61
+ }
62
+ if (isOpen) {
63
+ recordIdForAction = initialId ?? undefined;
64
+ apiResponse = null;
65
+ error = null;
66
+ }
67
+ }
68
+ initClient();
69
+ });
70
+
71
+ async function handleFormSubmit(data: any) {
72
+ if (!apiStore.prism) {
73
+ error = "API client (Prism) not ready.";
74
+ return;
75
+ }
76
+ loading = true; error = null; apiResponse = null;
77
+ try {
78
+ let result;
79
+ if (resourceType === 'table' || resourceType === 'view') {
80
+ if (!crudOps) {
81
+ error = "CRUD operations not initialized.";
82
+ loading = false; return;
83
+ }
84
+ switch (operation) {
85
+ case 'GET': result = await crudOps.findMany(data); break;
86
+ case 'POST': result = await crudOps.create(data); break;
87
+ case 'PUT':
88
+ const pkColumnPut = columns?.find((c: ColumnMetadata) => c.isPrimaryKey); // Added optional chaining
89
+ const idToUpdate = recordIdForAction ?? data[pkColumnPut?.name || 'id'];
90
+ if (!idToUpdate) throw new Error("Primary key value is required for PUT.");
91
+ const payloadPut = { ...data };
92
+ if (pkColumnPut) delete payloadPut[pkColumnPut.name];
93
+ result = await crudOps.update(idToUpdate, payloadPut);
94
+ break;
95
+ case 'DELETE':
96
+ const pkColumnDelete = columns?.find((c: ColumnMetadata) => c.isPrimaryKey); // Added optional chaining
97
+ const idToDelete = recordIdForAction ?? data[pkColumnDelete?.name || 'id'];
98
+ if (!idToDelete) throw new Error("Primary key value is required for DELETE.");
99
+ await crudOps.delete(idToDelete);
100
+ result = { message: `Record ${idToDelete} deleted successfully.` };
101
+ break;
102
+ }
103
+ } else if (resourceType === 'function') {
104
+ if (operation === 'POST') {
105
+ result = await apiStore.prism.executeFunction(schemaName, resourceName, data);
106
+ } else {
107
+ throw new Error(`Operation ${operation} not supported for functions via this modal.`);
108
+ }
109
+ }
110
+ apiResponse = result;
111
+ if (operation !== 'GET' || resourceType === 'function') {
112
+ setTimeout(() => onClose(), 1500);
113
+ }
114
+ } catch (e) {
115
+ console.error("API Operation Error:", e);
116
+ error = e instanceof Error ? e.message : String(e);
117
+ } finally {
118
+ loading = false;
119
+ }
120
+ }
121
+ let primaryKeyName = $derived(columns?.find((c: ColumnMetadata) => c.isPrimaryKey)?.name || 'id'); // Added optional chaining
122
+ </script>
123
+
124
+ {#if isOpen}
125
+ <dialog class="modal modal-open" open>
126
+ <div class="modal-box w-11/12 max-w-2xl">
127
+ <form method="dialog">
128
+ <button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onclick={onClose}>✕</button>
129
+ </form>
130
+ <h3 class="font-bold text-lg mb-4">
131
+ {operation}
132
+ <span class="badge badge-neutral badge-sm font-mono align-middle">{resourceType}</span>
133
+ <span class="font-mono badge badge-neutral align-middle">{schemaName}.{resourceName}</span>
134
+ </h3>
135
+
136
+ {#if error} <div role="alert" class="alert alert-error mb-4"> <!-- error display -->
137
+ <svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
138
+ <span>Error: {error}</span>
139
+ </div> {/if}
140
+
141
+ {#if resourceType === 'function'}
142
+ {#if operation === 'POST' && functionParams}
143
+ <RLFunctionForm params={functionParams} onSubmit={handleFormSubmit} {loading} />
144
+ {:else}
145
+ <p class="text-error">Configuration error: Operation {operation} not set up for functions or parameters missing.</p>
146
+ {/if}
147
+ {:else if operation === 'GET'}
148
+ <RLFilterForm columns={columns || []} onSubmit={handleFormSubmit} {loading} />
149
+ {:else if operation === 'POST'}
150
+ <RLResourceForm columns={columns || []} {operation} onSubmit={handleFormSubmit} {loading} />
151
+ {:else if operation === 'PUT'}
152
+ {#if !initialId && resourceType !== 'function'}
153
+ <div class="form-control mb-4">
154
+ <label class="label" for="put-record-id"><span class="label-text">Record {primaryKeyName} to Update <span class="text-error">*</span></span></label>
155
+ <input type="text" id="put-record-id" class="input input-bordered" bind:value={recordIdForAction} placeholder="Enter {primaryKeyName}" />
156
+ </div>
157
+ {/if}
158
+ <RLResourceForm columns={columns || []} {operation} onSubmit={handleFormSubmit} {loading} initialData={combinedInitialDataForPut} />
159
+ {:else if operation === 'DELETE'}
160
+ {#if !initialId && resourceType !== 'function'}
161
+ <div class="form-control mb-4">
162
+ <label class="label" for="delete-record-id"><span class="label-text">Record {primaryKeyName} to Delete <span class="text-error">*</span></span></label>
163
+ <input type="text" id="delete-record-id" class="input input-bordered" bind:value={recordIdForAction} placeholder="Enter {primaryKeyName}" />
164
+ </div>
165
+ {/if}
166
+ <p class="my-4">Are you sure you want to delete record with {primaryKeyName}: <strong class="font-mono">{recordIdForAction || ' (Enter ID above)'}</strong>?</p>
167
+ <div class="modal-action">
168
+ <button class="btn" onclick={onClose}>Cancel</button>
169
+ <button class="btn btn-error" onclick={() => handleFormSubmit({ [primaryKeyName]: recordIdForAction })} disabled={loading || !recordIdForAction}>
170
+ {#if loading} <span class="loading loading-spinner loading-xs"></span> {/if} Confirm Delete
171
+ </button>
172
+ </div>
173
+ {/if}
174
+
175
+ {#if apiResponse}
176
+ <div class="mt-6">
177
+ <h4 class="font-semibold">API Response:</h4>
178
+ <div class="mockup-code text-xs max-h-96 overflow-auto">
179
+ <pre data-prefix=">"><code>{JSON.stringify(apiResponse, null, 2)}</code></pre>
180
+ </div>
181
+ </div>
182
+ {/if}
183
+
184
+ {#if (operation !== 'DELETE' || resourceType === 'function' && operation === 'POST')} <!-- Simplified close button logic -->
185
+ <div class="modal-action mt-4"> <button class="btn" onclick={onClose}>Close</button> </div>
186
+ {/if}
187
+ </div>
188
+ <form method="dialog" class="modal-backdrop"> <button onclick={onClose}>close</button> </form>
189
+ </dialog>
190
+ {/if}
@@ -0,0 +1,18 @@
1
+ import type { ColumnMetadata, FunctionMetadata as PrismFunctionMetadata } from '@yrrrrrf/prism-ts';
2
+ type APIOperation = 'GET' | 'POST' | 'PUT' | 'DELETE';
3
+ type ModalResourceType = 'table' | 'view' | 'function';
4
+ type $$ComponentProps = {
5
+ isOpen: boolean;
6
+ onClose: () => void;
7
+ schemaName: string;
8
+ resourceName: string;
9
+ operation: APIOperation;
10
+ columns?: ColumnMetadata[];
11
+ functionParams?: PrismFunctionMetadata['parameters'] | null;
12
+ resourceType: ModalResourceType;
13
+ initialId?: string | number | null;
14
+ initialDataForPut?: Record<string, any>;
15
+ };
16
+ declare const RlApiOperationModal: import("svelte").Component<$$ComponentProps, {}, "">;
17
+ type RlApiOperationModal = ReturnType<typeof RlApiOperationModal>;
18
+ export default RlApiOperationModal;
@@ -0,0 +1,140 @@
1
+ <!-- src/lib/components/dataview/RLMetadataTable.svelte -->
2
+ <script lang="ts">
3
+ import type { ColumnMetadata, ColumnReference, EnumMetadata } from '@yrrrrrf/prism-ts';
4
+
5
+ let {
6
+ title,
7
+ itemType,
8
+ columns,
9
+ enumsInSchema = {},
10
+ onFkClick, // Callback prop
11
+ onEnumClick, // Callback prop
12
+ } = $props<{
13
+ title: string;
14
+ itemType: 'table' | 'view';
15
+ columns: ColumnMetadata[];
16
+ enumsInSchema?: Record<string, EnumMetadata>;
17
+ onFkClick?: (ref: ColumnReference) => void; // Make optional if not always needed
18
+ onEnumClick?: (enumData: { name: string; values: string[] }) => void; // Make optional
19
+ }>();
20
+
21
+ function formatReferenceText(ref: ColumnReference | undefined): string {
22
+ if (!ref) return '';
23
+ return `${ref.schema}.${ref.table}.${ref.column}`;
24
+ }
25
+
26
+ function handleFkButtonClick(ref: ColumnReference | undefined) {
27
+ if (ref && onFkClick) {
28
+ onFkClick(ref);
29
+ }
30
+ }
31
+
32
+ function getEnumForColumn(column: ColumnMetadata): EnumMetadata | undefined {
33
+ if (!column.isEnum || !enumsInSchema || Object.keys(enumsInSchema).length === 0) {
34
+ return undefined;
35
+ }
36
+
37
+ // Heuristic:
38
+ // 1. Exact match: column.name === enumMeta.name
39
+ // 2. Suffix match: enumMeta.name === `${column.name}_enum` (e.g., "status" -> "status_enum")
40
+ // 3. Prefix match: enumMeta.name === `${itemType}_${column.name}` (e.g., "enrollment_status" for table "enrollment", col "status")
41
+ // 4. More general: enumMeta.name includes column.name and "_enum" (e.g. "enrollment_status_enum" for "status")
42
+ // The prism-py output for enrollment.status shows "Enum(enrollment_status)".
43
+ // The actual enum name from `dt-schemas.json` (API response) for student schema is `status_enum`.
44
+ // If `column.name` is 'status' and `isEnum` is true, we need to find `status_enum`.
45
+
46
+ const colNameLower = column.name.toLowerCase();
47
+ for (const enumKey in enumsInSchema) {
48
+ const enumMeta = enumsInSchema[enumKey];
49
+ const enumNameLower = enumMeta.name.toLowerCase();
50
+
51
+ if (enumNameLower === colNameLower) return enumMeta; // Exact name match
52
+ if (enumNameLower === `${colNameLower}_enum`) return enumMeta; // status -> status_enum
53
+ if (enumNameLower === `${title.toLowerCase()}_${colNameLower}`) return enumMeta; // enrollment_status for table enrollment, column status
54
+ // Check if the enum name is specifically related to the column, common pattern is `table_column_enum` or `column_enum`
55
+ if (enumNameLower.includes(colNameLower) && enumNameLower.endsWith("_enum")) return enumMeta;
56
+
57
+
58
+ }
59
+ // If after specific checks nothing, try to find any enum that might be related by column name part
60
+ // This is very broad, use with caution or remove if too many false positives.
61
+ // for (const enumKey in enumsInSchema) {
62
+ // if (enumsInSchema[enumKey].name.toLowerCase().includes(colNameLower)) {
63
+ // return enumsInSchema[enumKey];
64
+ // }
65
+ // }
66
+ return undefined;
67
+ }
68
+
69
+ function handleEnumBadgeClick(enumMeta: EnumMetadata | undefined) {
70
+ if (enumMeta && onEnumClick) {
71
+ onEnumClick({ name: enumMeta.name, values: enumMeta.values });
72
+ }
73
+ }
74
+
75
+ </script>
76
+
77
+ <div class="card bg-base-100 shadow-xl overflow-hidden">
78
+ <div class="card-body p-0">
79
+ {#if columns && columns.length > 0}
80
+ <div class="overflow-x-auto">
81
+ <table class="table table-sm w-full">
82
+ <thead>
83
+ <tr>
84
+ <th class="w-2/5 pl-4">Column</th>
85
+ <th class="w-3/5 pr-4">Details</th>
86
+ </tr>
87
+ </thead>
88
+ <tbody>
89
+ {#each columns as column (column.name)}
90
+ {@const enumInfo = getEnumForColumn(column)}
91
+ <tr class="hover">
92
+ <td class="align-top py-2.5 pl-4">
93
+ <div class="flex items-baseline">
94
+ <span class="font-medium">{column.name}</span>
95
+ {#if !column.nullable}
96
+ <span class="text-error ml-1 select-none" title="Required field">*</span>
97
+ {/if}
98
+ </div>
99
+ <div class="font-mono text-xs text-base-content/60 italic mt-0.5">{column.type}</div>
100
+ </td>
101
+ <td class="align-top py-2.5 pr-4 space-x-1.5">
102
+ {#if column.isPrimaryKey}
103
+ <span class="badge badge-accent badge-sm font-semibold">PK</span>
104
+ {/if}
105
+ {#if column.references}
106
+ <span class="badge badge-secondary badge-sm">FK</span>
107
+ <button
108
+ class="link link-hover text-xs font-mono !text-info normal-case"
109
+ onclick={() => handleFkButtonClick(column.references)}
110
+ title="Navigate to {formatReferenceText(column.references)}"
111
+ >
112
+ {formatReferenceText(column.references)}
113
+ </button>
114
+ {/if}
115
+ {#if enumInfo}
116
+ <button
117
+ class="badge badge-warning badge-sm hover:shadow-md transition-shadow normal-case"
118
+ onclick={() => handleEnumBadgeClick(enumInfo)}
119
+ title="Enum: {enumInfo.name} (Click to see values)"
120
+ >
121
+ <span class="mr-1 opacity-70">Enum:</span>{enumInfo.name}
122
+ </button>
123
+ {:else if column.isEnum}
124
+ <span class="badge badge-ghost badge-sm normal-case" title="This column uses an enumerated type (details not auto-linked).">
125
+ <span class="opacity-70">Enum</span>
126
+ </span>
127
+ {/if}
128
+ </td>
129
+ </tr>
130
+ {/each}
131
+ </tbody>
132
+ </table>
133
+ </div>
134
+ {:else}
135
+ <div class="p-6 text-center text-neutral-content/70">
136
+ No columns defined for this {itemType}.
137
+ </div>
138
+ {/if}
139
+ </div>
140
+ </div>
@@ -0,0 +1,15 @@
1
+ import type { ColumnMetadata, ColumnReference, EnumMetadata } from '@yrrrrrf/prism-ts';
2
+ type $$ComponentProps = {
3
+ title: string;
4
+ itemType: 'table' | 'view';
5
+ columns: ColumnMetadata[];
6
+ enumsInSchema?: Record<string, EnumMetadata>;
7
+ onFkClick?: (ref: ColumnReference) => void;
8
+ onEnumClick?: (enumData: {
9
+ name: string;
10
+ values: string[];
11
+ }) => void;
12
+ };
13
+ declare const RlMetadataTable: import("svelte").Component<$$ComponentProps, {}, "">;
14
+ type RlMetadataTable = ReturnType<typeof RlMetadataTable>;
15
+ export default RlMetadataTable;