Skip to content

Commit 3ab9cde

Browse files
committed
Added raw and functions features to superdebug, enhanced jsdoc types as well as component documentation
1 parent 0f7fa06 commit 3ab9cde

File tree

3 files changed

+403
-129
lines changed

3 files changed

+403
-129
lines changed

src/lib/client/SuperDebug.svelte

Lines changed: 167 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,69 @@
11
<script>
22
import { page } from '$app/stores';
3+
import { onDestroy } from 'svelte';
34
5+
/**
6+
* @typedef {unknown | Promise<unknown>} EncodeableData
7+
* @typedef {import('svelte/store').Readable<EncodeableData>} EncodeableDataStore
8+
*
9+
* @typedef {EncodeableData | EncodeableDataStore} DebugData
10+
*/
11+
12+
/**
13+
* Data to be displayed as pretty JSON.
14+
*
15+
* @type {DebugData}
16+
*/
17+
export let data;
18+
/**
19+
* Controls when the component should be displayed.
20+
*
21+
* Default: `true`.
22+
*/
423
export let display = true;
24+
/**
25+
* Controls when to show the http status code of the current page (reflecs the status code of the last request).
26+
*
27+
* Default is `true`.
28+
*/
529
export let status = true;
6-
/** @type {any} */
7-
export let data;
30+
/**
31+
* Optional label to identify the component easily.
32+
*/
33+
export let label = '';
34+
/**
35+
* Controls the maximum length of a string field of the data prop.
36+
*
37+
* Default is `120` characters.
38+
*/
839
export let stringTruncate = 120;
9-
/** @type {HTMLPreElement | undefined} */
40+
/**
41+
* Reference to the pre element that contains the shown d
42+
*
43+
* @type {HTMLPreElement | undefined}
44+
*/
1045
export let ref = undefined;
11-
export let label = '';
46+
/**
47+
* Controls if the data prop should be treated as a promise (skips promise detection when true).
48+
*
49+
* Default is `false`.
50+
*/
1251
export let promise = false;
52+
/**
53+
* Controls if the data prop should be treated as a plain object (skips promise and store detection when true, prevails over promise prop).
54+
*
55+
* Default is `false`.
56+
*/
57+
export let raw = false;
58+
/**
59+
* Enables the display of fields of the data prop that are functions.
60+
*
61+
* Default is `false`.
62+
*/
63+
export let functions = false;
1364
1465
/**
15-
* @param {any} json
66+
* @param {unknown} json
1667
* @returns {string}
1768
*/
1869
function syntaxHighlight(json) {
@@ -31,13 +82,17 @@
3182
if (typeof value === 'bigint') {
3283
return '#}BI#' + value;
3384
}
85+
if (typeof value === 'function' && functions) {
86+
return '#}F#' + `[function ${value.name}]`;
87+
}
3488
return value;
3589
},
3690
2
3791
)
3892
.replace(/&/g, '&amp;')
3993
.replace(/</g, '&lt;')
4094
.replace(/>/g, '&gt;');
95+
4196
return encodedString.replace(
4297
/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g,
4398
function (match) {
@@ -67,6 +122,9 @@
67122
} else if (match.startsWith('"#}BI#')) {
68123
cls = 'bigint';
69124
match = match.slice(6, -1) + 'n';
125+
} else if (match.startsWith('"#}F#')) {
126+
cls = 'function';
127+
match = match.slice(5, -1);
70128
}
71129
}
72130
} else if (/true|false/.test(match)) {
@@ -80,13 +138,65 @@
80138
}
81139
82140
/**
83-
* @param {any} json
141+
* @param {EncodeableData} json
84142
* @returns {Promise<string>}
85143
*/
86144
async function promiseSyntaxHighlight(json) {
87145
json = await json;
88146
return syntaxHighlight(json);
89147
}
148+
149+
/**
150+
* @param {EncodeableData} data
151+
* @param {boolean} raw
152+
* @param {boolean} promise
153+
* @returns {data is Promise<unknown>}
154+
*/
155+
function assertPromise(data, raw, promise) {
156+
if (raw) {
157+
return false;
158+
}
159+
return (
160+
promise ||
161+
(typeof data === 'object' &&
162+
data !== null &&
163+
'then' in data &&
164+
typeof data['then'] === 'function')
165+
);
166+
}
167+
168+
/**
169+
* @param {DebugData} data
170+
* @param {boolean} raw
171+
* @returns {data is EncodeableDataStore}
172+
*/
173+
function assertStore(data, raw) {
174+
if (raw) {
175+
return false;
176+
}
177+
return (
178+
typeof data === 'object' &&
179+
data !== null &&
180+
'subscribe' in data &&
181+
typeof data['subscribe'] === 'function'
182+
);
183+
}
184+
185+
/** @type {EncodeableData} */
186+
let debugData;
187+
188+
let dataIsStore = false;
189+
if (assertStore(data, raw)) {
190+
dataIsStore = true;
191+
const unsubscribe = data.subscribe((value) => {
192+
debugData = value;
193+
});
194+
onDestroy(unsubscribe);
195+
}
196+
197+
$: if (!dataIsStore) {
198+
debugData = data;
199+
}
90200
</script>
91201

92202
{#if display}
@@ -112,14 +222,63 @@
112222
class="super-debug--pre {label === '' ? 'pt-4' : 'pt-0'}"
113223
bind:this={ref}><code class="super-debug--code"
114224
><slot
115-
>{#if promise}{#await promiseSyntaxHighlight(data)}<div>Loading data</div>{:then result}{@html result}{/await}{:else}{@html syntaxHighlight(
116-
data
225+
>{#if assertPromise(debugData, raw, promise)}{#await promiseSyntaxHighlight(debugData)}<div>Loading data...</div>{:then result}{@html result}{/await}{:else}{@html syntaxHighlight(
226+
debugData
117227
)}{/if}</slot
118228
></code
119229
></pre>
120230
</div>
121231
{/if}
122232

233+
<!--
234+
@component
235+
236+
SuperDebug is a debugging component that gives you colorized and nicely formatted output for any data structure, usually $form.
237+
238+
Other use cases includes debugging plain objects, promises, stores and more.
239+
240+
More info:
241+
- [API](https://superforms.rocks/api#superdebug)
242+
- [Examples](https://github.com/ciscoheat/sveltekit-superforms/tree/main/src/routes/super-debug)
243+
244+
**Short example:**
245+
246+
```svelte
247+
<script>
248+
import SuperDebug from 'sveltekit-superforms/client/SuperDebug.svelte';
249+
250+
export let data;
251+
252+
const { errors, form, enhance } = superForm(data.form);
253+
</script>
254+
255+
<form method="POST" use:enhance>
256+
<label>
257+
Name<br />
258+
<input
259+
name="name"
260+
type="text"
261+
aria-invalid={$errors.name ? 'true' : undefined}
262+
bind:value={$form.name}
263+
/>
264+
{#if $errors.name}<span class="invalid">{$errors.name}</span>{/if}
265+
</label>
266+
<label>
267+
Email<br />
268+
<input
269+
name="email"
270+
type="email"
271+
aria-invalid={$errors.email ? 'true' : undefined}
272+
bind:value={$form.email}
273+
/>
274+
{#if $errors.email}<span class="invalid">{$errors.email}</span>{/if}
275+
</label>
276+
<button>Submit</button>
277+
</form>
278+
<SuperDebug data={$form} label="My form data" />
279+
```
280+
-->
281+
123282
<style>
124283
.absolute {
125284
position: absolute;

src/routes/super-debug/+page.server.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,23 @@ const schema = z.object({
99
phone_number: z.string().min(1, 'Phone number is required.')
1010
});
1111

12+
const bigSchema = z.object({
13+
full_name: z.string().min(1, 'Full Name is required.'),
14+
email: z.string().min(1, 'Email is required.').email('Email is invalid.'),
15+
age: z.number().min(1, 'Age is required.').max(100, 'Age is invalid.'),
16+
optional: z.string().optional(),
17+
nullable: z.string().nullable(),
18+
today: z.date().min(new Date(2021, 0, 1), 'Date is invalid.').default(() => new Date()).optional(),
19+
});
20+
1221
export const load = (async ({ request }) => {
1322
const form = await superValidate(request, schema);
23+
const bigForm = await superValidate(request, bigSchema);
1424

15-
return { form };
25+
return {
26+
form,
27+
bigForm
28+
};
1629
}) satisfies PageServerLoad;
1730

1831
export const actions = {

0 commit comments

Comments
 (0)