litestar-vite-plugin 0.15.0-alpha.3 → 0.15.0-alpha.5
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 +56 -42
- package/dist/js/astro.d.ts +6 -0
- package/dist/js/astro.js +42 -7
- package/dist/js/helpers/htmx.d.ts +68 -0
- package/dist/js/helpers/htmx.js +494 -0
- package/dist/js/helpers/index.d.ts +13 -5
- package/dist/js/helpers/index.js +14 -6
- package/dist/js/index.d.ts +35 -13
- package/dist/js/index.js +233 -60
- package/dist/js/inertia-helpers/index.d.ts +6 -1
- package/dist/js/inertia-helpers/index.js +7 -4
- package/dist/js/litestar-meta.js +20 -4
- package/dist/js/nuxt.d.ts +6 -0
- package/dist/js/nuxt.js +39 -4
- package/dist/js/sveltekit.d.ts +6 -0
- package/dist/js/sveltekit.js +34 -4
- package/package.json +18 -11
- package/tools/clean.js +1 -1
- package/dist/js/helpers/routes.d.ts +0 -159
- package/dist/js/helpers/routes.js +0 -302
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Litestar HTMX Extension
|
|
3
|
+
*
|
|
4
|
+
* Lightweight JSON templating for HTMX with CSRF support.
|
|
5
|
+
*
|
|
6
|
+
* Features:
|
|
7
|
+
* - `hx-swap="json"` - Client-side JSON templating
|
|
8
|
+
* - Automatic CSRF token injection
|
|
9
|
+
* - Template syntax: `ls-for`, `ls-if`, `:attr`, `@event`, `${expr}`
|
|
10
|
+
*
|
|
11
|
+
* For typed routes, import from your generated routes file:
|
|
12
|
+
* ```ts
|
|
13
|
+
* import { route } from '@/generated/routes'
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```html
|
|
18
|
+
* <div hx-get="/api/books" hx-swap="json" hx-ext="litestar">
|
|
19
|
+
* <template ls-for="book in $data">
|
|
20
|
+
* <article :id="`book-${book.id}`">
|
|
21
|
+
* <h3>${book.title}</h3>
|
|
22
|
+
* <p>${book.author} • ${book.year}</p>
|
|
23
|
+
* </article>
|
|
24
|
+
* </template>
|
|
25
|
+
* </div>
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* @module
|
|
29
|
+
*/
|
|
30
|
+
import { getCsrfToken } from "./csrf.js";
|
|
31
|
+
const DEBUG = typeof process !== "undefined" && process.env?.NODE_ENV !== "production";
|
|
32
|
+
const cache = new Map();
|
|
33
|
+
const memoStore = new WeakMap();
|
|
34
|
+
// =============================================================================
|
|
35
|
+
// Registration
|
|
36
|
+
// =============================================================================
|
|
37
|
+
export function registerHtmxExtension() {
|
|
38
|
+
if (typeof window === "undefined" || !window.htmx)
|
|
39
|
+
return;
|
|
40
|
+
window.htmx.defineExtension("litestar", {
|
|
41
|
+
onEvent(name, evt) {
|
|
42
|
+
if (name === "htmx:configRequest") {
|
|
43
|
+
const token = getCsrfToken();
|
|
44
|
+
if (token)
|
|
45
|
+
evt.detail.headers["X-CSRF-Token"] = token;
|
|
46
|
+
}
|
|
47
|
+
return true;
|
|
48
|
+
},
|
|
49
|
+
transformResponse(text, xhr) {
|
|
50
|
+
if (xhr.getResponseHeader("content-type")?.includes("application/json")) {
|
|
51
|
+
const d = document.createElement("div");
|
|
52
|
+
d.textContent = text;
|
|
53
|
+
return d.innerHTML;
|
|
54
|
+
}
|
|
55
|
+
return text;
|
|
56
|
+
},
|
|
57
|
+
isInlineSwap: (s) => s === "json",
|
|
58
|
+
handleSwap(style, target, frag) {
|
|
59
|
+
if (style === "json") {
|
|
60
|
+
try {
|
|
61
|
+
swapJson(target, JSON.parse(frag.textContent ?? ""));
|
|
62
|
+
}
|
|
63
|
+
catch (e) {
|
|
64
|
+
target.innerHTML = `<div style="color:red;padding:1rem">${e}</div>`;
|
|
65
|
+
}
|
|
66
|
+
return [target];
|
|
67
|
+
}
|
|
68
|
+
return [];
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
if (DEBUG)
|
|
72
|
+
console.log("[litestar] htmx extension registered");
|
|
73
|
+
}
|
|
74
|
+
// =============================================================================
|
|
75
|
+
// Note: hx-route functionality removed - use generated routes directly
|
|
76
|
+
// Import route from your generated routes.ts file instead:
|
|
77
|
+
// import { route } from '@/generated/routes'
|
|
78
|
+
// element.setAttribute('hx-get', route('my_route', { id: 123 }))
|
|
79
|
+
// =============================================================================
|
|
80
|
+
// =============================================================================
|
|
81
|
+
// JSON Swap Entry Point
|
|
82
|
+
// =============================================================================
|
|
83
|
+
export function swapJson(el, data) {
|
|
84
|
+
swap(el, rootCtx(data));
|
|
85
|
+
}
|
|
86
|
+
// =============================================================================
|
|
87
|
+
// Core Swap Logic
|
|
88
|
+
// =============================================================================
|
|
89
|
+
function swap(node, ctx, end, parse = false) {
|
|
90
|
+
// Text nodes: interpolate ${expr}
|
|
91
|
+
if (node.nodeType === 3) {
|
|
92
|
+
const g = memo(node, "t", () => {
|
|
93
|
+
const t = node.textContent ?? "";
|
|
94
|
+
return compileTextExpr(t);
|
|
95
|
+
});
|
|
96
|
+
if (!g)
|
|
97
|
+
return null;
|
|
98
|
+
if (!parse)
|
|
99
|
+
node.textContent = String(g(ctx) ?? "");
|
|
100
|
+
return node;
|
|
101
|
+
}
|
|
102
|
+
// Elements
|
|
103
|
+
if (node.nodeType === 1) {
|
|
104
|
+
const el = node;
|
|
105
|
+
// Template: structural directives
|
|
106
|
+
if (el.nodeName === "TEMPLATE") {
|
|
107
|
+
return forDir(el, ctx, end, parse) ?? ifDir(el, ctx, end, parse);
|
|
108
|
+
}
|
|
109
|
+
// Process attribute directives
|
|
110
|
+
let c = ctx;
|
|
111
|
+
const handlers = memo(el, "a", () => {
|
|
112
|
+
const h = [];
|
|
113
|
+
for (const attr of Array.from(el.attributes)) {
|
|
114
|
+
const d = directives.find((x) => x.match(attr));
|
|
115
|
+
if (d) {
|
|
116
|
+
const handler = d.create(el, attr);
|
|
117
|
+
if (handler)
|
|
118
|
+
h.push(handler);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return h;
|
|
122
|
+
});
|
|
123
|
+
for (const h of handlers) {
|
|
124
|
+
if (!parse && c) {
|
|
125
|
+
const r = h(c, el);
|
|
126
|
+
if (r !== undefined)
|
|
127
|
+
c = r;
|
|
128
|
+
}
|
|
129
|
+
if (!c)
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
if (c === false)
|
|
133
|
+
return el;
|
|
134
|
+
if (!c)
|
|
135
|
+
return null;
|
|
136
|
+
// Recurse children
|
|
137
|
+
swapKids(el.firstChild, undefined, c, parse);
|
|
138
|
+
return el;
|
|
139
|
+
}
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
function swapKids(start, end, ctx, parse = false) {
|
|
143
|
+
let current = start;
|
|
144
|
+
while (current && current !== end) {
|
|
145
|
+
const r = swap(current, ctx, end, parse);
|
|
146
|
+
current = (r ?? current).nextSibling;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
const directives = [
|
|
150
|
+
// :attr="expr" - attribute binding
|
|
151
|
+
{
|
|
152
|
+
match: (a) => a.name.startsWith(":"),
|
|
153
|
+
create(_el, a) {
|
|
154
|
+
const name = a.name.slice(1);
|
|
155
|
+
const g = expr(a.value);
|
|
156
|
+
if (!g)
|
|
157
|
+
return null;
|
|
158
|
+
return (ctx, el) => {
|
|
159
|
+
const v = g(ctx);
|
|
160
|
+
if (name === "class" && typeof v === "object" && v) {
|
|
161
|
+
for (const [k, on] of Object.entries(v))
|
|
162
|
+
el.classList.toggle(k, Boolean(on));
|
|
163
|
+
}
|
|
164
|
+
else if (v == null || v === false) {
|
|
165
|
+
el.removeAttribute(name);
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
el.setAttribute(name, v === true ? "" : String(v));
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
// @event="expr" - event binding
|
|
174
|
+
{
|
|
175
|
+
match: (a) => a.name.startsWith("@"),
|
|
176
|
+
create(_el, a) {
|
|
177
|
+
const name = a.name.slice(1);
|
|
178
|
+
const g = expr(a.value);
|
|
179
|
+
if (!g)
|
|
180
|
+
return null;
|
|
181
|
+
let bound = false;
|
|
182
|
+
return (ctx, el) => {
|
|
183
|
+
if (!bound) {
|
|
184
|
+
bound = true;
|
|
185
|
+
el.addEventListener(name, (e) => g({ ...ctx, $event: e }));
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
// ls-scope="expr" - change context
|
|
191
|
+
{
|
|
192
|
+
match: (a) => a.name === "ls-scope",
|
|
193
|
+
create(_, a) {
|
|
194
|
+
const g = expr(a.value);
|
|
195
|
+
if (!g)
|
|
196
|
+
return null;
|
|
197
|
+
return (ctx) => {
|
|
198
|
+
const d = g(ctx);
|
|
199
|
+
return d ? childCtx(ctx, d) : false;
|
|
200
|
+
};
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
// ls-text="expr" - text content
|
|
204
|
+
{
|
|
205
|
+
match: (a) => a.name === "ls-text",
|
|
206
|
+
create(_, a) {
|
|
207
|
+
const g = expr(a.value);
|
|
208
|
+
if (!g)
|
|
209
|
+
return null;
|
|
210
|
+
return (ctx, el) => {
|
|
211
|
+
el.textContent = String(g(ctx) ?? "");
|
|
212
|
+
};
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
// ls-html="expr" - innerHTML (use carefully)
|
|
216
|
+
{
|
|
217
|
+
match: (a) => a.name === "ls-html",
|
|
218
|
+
create(_, a) {
|
|
219
|
+
const g = expr(a.value);
|
|
220
|
+
if (!g)
|
|
221
|
+
return null;
|
|
222
|
+
return (ctx, el) => {
|
|
223
|
+
el.innerHTML = String(g(ctx) ?? "");
|
|
224
|
+
};
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
// ls-show/ls-hide
|
|
228
|
+
{
|
|
229
|
+
match: (a) => a.name === "ls-show",
|
|
230
|
+
create(_, a) {
|
|
231
|
+
const g = expr(a.value);
|
|
232
|
+
if (!g)
|
|
233
|
+
return null;
|
|
234
|
+
return (ctx, el) => {
|
|
235
|
+
;
|
|
236
|
+
el.style.display = g(ctx) ? "" : "none";
|
|
237
|
+
};
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
match: (a) => a.name === "ls-hide",
|
|
242
|
+
create(_, a) {
|
|
243
|
+
const g = expr(a.value);
|
|
244
|
+
if (!g)
|
|
245
|
+
return null;
|
|
246
|
+
return (ctx, el) => {
|
|
247
|
+
;
|
|
248
|
+
el.style.display = g(ctx) ? "none" : "";
|
|
249
|
+
};
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
// name attr on inputs - auto-bind from context
|
|
253
|
+
{
|
|
254
|
+
match: (a) => a.name === "name",
|
|
255
|
+
create(el, a) {
|
|
256
|
+
if (!(el instanceof HTMLInputElement || el instanceof HTMLSelectElement || el instanceof HTMLTextAreaElement))
|
|
257
|
+
return null;
|
|
258
|
+
const key = a.value;
|
|
259
|
+
return (ctx, el) => {
|
|
260
|
+
const v = ctx[key];
|
|
261
|
+
if (v === undefined)
|
|
262
|
+
return;
|
|
263
|
+
const inp = el;
|
|
264
|
+
if (inp.type === "checkbox")
|
|
265
|
+
inp.checked = Boolean(v);
|
|
266
|
+
else if (inp.type === "radio")
|
|
267
|
+
inp.checked = v === inp.value;
|
|
268
|
+
else
|
|
269
|
+
inp.value = String(v ?? "");
|
|
270
|
+
};
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
];
|
|
274
|
+
// =============================================================================
|
|
275
|
+
// Structural: ls-for
|
|
276
|
+
// =============================================================================
|
|
277
|
+
function forDir(tpl, ctx, _parentEnd, parse = false) {
|
|
278
|
+
const raw = tpl.getAttribute("ls-for") ?? tpl.getAttribute("ls-each");
|
|
279
|
+
if (!raw)
|
|
280
|
+
return null;
|
|
281
|
+
preparseTpl(tpl);
|
|
282
|
+
if (parse)
|
|
283
|
+
return tpl;
|
|
284
|
+
// Parse "item in items" or just "items"
|
|
285
|
+
const m = raw.match(/^\s*(\w+)\s+in\s+(.+)$/);
|
|
286
|
+
const [alias, listExpr] = m ? [m[1], m[2]] : [null, raw];
|
|
287
|
+
const g = memo(tpl, "g", () => expr(listExpr));
|
|
288
|
+
if (!g)
|
|
289
|
+
return null;
|
|
290
|
+
const items = toList(g(ctx), tpl, ctx, alias);
|
|
291
|
+
const end = memo(tpl, "end", () => insertComment(tpl, "/ls-for"));
|
|
292
|
+
const old = memo(tpl, "list", () => collectComments(tpl, end));
|
|
293
|
+
let i = 0;
|
|
294
|
+
for (; i < items.length; i++) {
|
|
295
|
+
const [key, item] = items[i];
|
|
296
|
+
const c = childCtx(ctx, item, i, key);
|
|
297
|
+
if (alias)
|
|
298
|
+
c[alias] = item;
|
|
299
|
+
if (i < old.length && old[i][0] === key) {
|
|
300
|
+
// Same key: update in place
|
|
301
|
+
swapKids(old[i][1].nextSibling, old[i + 1]?.[1] ?? end, c);
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
// Insert new
|
|
305
|
+
const clone = tpl.content.cloneNode(true);
|
|
306
|
+
const comment = document.createComment(key);
|
|
307
|
+
const ref = old[i]?.[1] ?? end;
|
|
308
|
+
ref.parentNode?.insertBefore(comment, ref);
|
|
309
|
+
ref.parentNode?.insertBefore(clone, ref);
|
|
310
|
+
swapKids(comment.nextSibling, ref, c);
|
|
311
|
+
// Remove old if exists
|
|
312
|
+
if (i < old.length) {
|
|
313
|
+
removeBetween(old[i][1], old[i + 1]?.[1] ?? end);
|
|
314
|
+
old[i][1].remove();
|
|
315
|
+
}
|
|
316
|
+
old[i] = [key, comment];
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
// Remove excess
|
|
320
|
+
while (old.length > items.length) {
|
|
321
|
+
const popped = old.pop();
|
|
322
|
+
if (!popped)
|
|
323
|
+
break;
|
|
324
|
+
const [, c] = popped;
|
|
325
|
+
removeBetween(c, old[old.length]?.[1] ?? end);
|
|
326
|
+
c.remove();
|
|
327
|
+
}
|
|
328
|
+
return end;
|
|
329
|
+
}
|
|
330
|
+
// =============================================================================
|
|
331
|
+
// Structural: ls-if
|
|
332
|
+
// =============================================================================
|
|
333
|
+
function ifDir(tpl, ctx, _parentEnd, parse = false) {
|
|
334
|
+
const raw = tpl.getAttribute("ls-if");
|
|
335
|
+
if (!raw)
|
|
336
|
+
return null;
|
|
337
|
+
preparseTpl(tpl);
|
|
338
|
+
// Find else template
|
|
339
|
+
const elseTpl = tpl.nextElementSibling?.nodeName === "TEMPLATE" && tpl.nextElementSibling.hasAttribute("ls-else") ? tpl.nextElementSibling : null;
|
|
340
|
+
if (elseTpl)
|
|
341
|
+
preparseTpl(elseTpl);
|
|
342
|
+
if (parse)
|
|
343
|
+
return elseTpl ?? tpl;
|
|
344
|
+
const g = memo(tpl, "g", () => expr(raw));
|
|
345
|
+
const anchor = memo(tpl, "anchor", () => insertComment(tpl, ""));
|
|
346
|
+
const end = memo(tpl, "end", () => insertComment(anchor, "/ls-if"));
|
|
347
|
+
const show = g?.(ctx);
|
|
348
|
+
if (show) {
|
|
349
|
+
if (anchor.data !== "if") {
|
|
350
|
+
anchor.data = "if";
|
|
351
|
+
removeBetween(anchor.nextSibling, end);
|
|
352
|
+
end.parentNode?.insertBefore(tpl.content.cloneNode(true), end);
|
|
353
|
+
}
|
|
354
|
+
swapKids(anchor.nextSibling, end, ctx);
|
|
355
|
+
}
|
|
356
|
+
else if (elseTpl) {
|
|
357
|
+
if (anchor.data !== "else") {
|
|
358
|
+
anchor.data = "else";
|
|
359
|
+
removeBetween(anchor.nextSibling, end);
|
|
360
|
+
end.parentNode?.insertBefore(elseTpl.content.cloneNode(true), end);
|
|
361
|
+
}
|
|
362
|
+
swapKids(anchor.nextSibling, end, ctx);
|
|
363
|
+
}
|
|
364
|
+
else {
|
|
365
|
+
anchor.data = "";
|
|
366
|
+
removeBetween(anchor.nextSibling, end);
|
|
367
|
+
}
|
|
368
|
+
return end;
|
|
369
|
+
}
|
|
370
|
+
// =============================================================================
|
|
371
|
+
// Context
|
|
372
|
+
// =============================================================================
|
|
373
|
+
function rootCtx(data) {
|
|
374
|
+
const ctx = {
|
|
375
|
+
$data: data,
|
|
376
|
+
// Note: route and navigate are optional - users can provide their own
|
|
377
|
+
// by importing from their generated routes.ts file
|
|
378
|
+
};
|
|
379
|
+
if (data && typeof data === "object")
|
|
380
|
+
Object.setPrototypeOf(ctx, data);
|
|
381
|
+
return ctx;
|
|
382
|
+
}
|
|
383
|
+
function childCtx(parent, data, index, key) {
|
|
384
|
+
const ctx = Object.create(data && typeof data === "object" ? data : null);
|
|
385
|
+
ctx.$data = data;
|
|
386
|
+
ctx.$parent = parent;
|
|
387
|
+
ctx.$index = index;
|
|
388
|
+
ctx.$key = key;
|
|
389
|
+
ctx.route = parent.route;
|
|
390
|
+
ctx.navigate = parent.navigate;
|
|
391
|
+
return ctx;
|
|
392
|
+
}
|
|
393
|
+
// =============================================================================
|
|
394
|
+
// Expression Compiler
|
|
395
|
+
// =============================================================================
|
|
396
|
+
function expr(s) {
|
|
397
|
+
if (!s)
|
|
398
|
+
return null;
|
|
399
|
+
const cached = cache.get(s);
|
|
400
|
+
if (cached !== undefined)
|
|
401
|
+
return cached;
|
|
402
|
+
try {
|
|
403
|
+
const fn = new Function("ctx", `with(ctx){return(${s})}`);
|
|
404
|
+
cache.set(s, fn);
|
|
405
|
+
return fn;
|
|
406
|
+
}
|
|
407
|
+
catch {
|
|
408
|
+
cache.set(s, null);
|
|
409
|
+
return null;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
/** Compile text with ${expr} interpolation - escapes backticks and backslashes */
|
|
413
|
+
function compileTextExpr(t) {
|
|
414
|
+
if (!t.includes("${"))
|
|
415
|
+
return null;
|
|
416
|
+
// Escape backticks and backslashes for safe template literal compilation
|
|
417
|
+
const escaped = t.replace(/[`\\]/g, "\\$&");
|
|
418
|
+
return expr(`\`${escaped}\``);
|
|
419
|
+
}
|
|
420
|
+
// =============================================================================
|
|
421
|
+
// Utilities
|
|
422
|
+
// =============================================================================
|
|
423
|
+
function memo(node, key, fn) {
|
|
424
|
+
let store = memoStore.get(node);
|
|
425
|
+
if (!store) {
|
|
426
|
+
store = {};
|
|
427
|
+
memoStore.set(node, store);
|
|
428
|
+
}
|
|
429
|
+
if (!(key in store)) {
|
|
430
|
+
store[key] = fn();
|
|
431
|
+
}
|
|
432
|
+
return store[key];
|
|
433
|
+
}
|
|
434
|
+
function preparseTpl(t) {
|
|
435
|
+
memo(t, "p", () => {
|
|
436
|
+
swapKids(t.content.firstChild, undefined, {}, true);
|
|
437
|
+
return true;
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
function toList(items, tpl, ctx, alias) {
|
|
441
|
+
const keyAttr = tpl.getAttribute("ls-key");
|
|
442
|
+
const keyFn = keyAttr ? expr(keyAttr) : null;
|
|
443
|
+
if (Array.isArray(items)) {
|
|
444
|
+
return items.map((item, i) => {
|
|
445
|
+
if (!keyFn)
|
|
446
|
+
return [String(i), item];
|
|
447
|
+
// Create a child context with the alias so key expressions like "item.id" work
|
|
448
|
+
const keyCtx = childCtx(ctx, item, i);
|
|
449
|
+
if (alias)
|
|
450
|
+
keyCtx[alias] = item;
|
|
451
|
+
return [String(keyFn(keyCtx)), item];
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
if (items && typeof items === "object") {
|
|
455
|
+
return Object.entries(items);
|
|
456
|
+
}
|
|
457
|
+
return [];
|
|
458
|
+
}
|
|
459
|
+
function insertComment(after, text) {
|
|
460
|
+
const c = document.createComment(text);
|
|
461
|
+
after.parentNode?.insertBefore(c, after.nextSibling);
|
|
462
|
+
return c;
|
|
463
|
+
}
|
|
464
|
+
function collectComments(tpl, end) {
|
|
465
|
+
const list = [];
|
|
466
|
+
let n = tpl.nextSibling;
|
|
467
|
+
while (n && n !== end) {
|
|
468
|
+
if (n.nodeType === 8 && !n.data.startsWith("/")) {
|
|
469
|
+
list.push([n.data, n]);
|
|
470
|
+
}
|
|
471
|
+
n = n.nextSibling;
|
|
472
|
+
}
|
|
473
|
+
return list;
|
|
474
|
+
}
|
|
475
|
+
function removeBetween(start, end) {
|
|
476
|
+
let current = start;
|
|
477
|
+
while (current && current !== end) {
|
|
478
|
+
const next = current.nextSibling;
|
|
479
|
+
current.parentNode?.removeChild(current);
|
|
480
|
+
current = next;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
// =============================================================================
|
|
484
|
+
// Public API
|
|
485
|
+
// =============================================================================
|
|
486
|
+
export function setDebug(_on) {
|
|
487
|
+
// Debug flag is const, this is a no-op placeholder
|
|
488
|
+
}
|
|
489
|
+
export function addDirective(dir) {
|
|
490
|
+
directives.push(dir);
|
|
491
|
+
}
|
|
492
|
+
// Auto-register
|
|
493
|
+
if (typeof window !== "undefined" && window.htmx)
|
|
494
|
+
registerHtmxExtension();
|
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @example
|
|
8
8
|
* ```ts
|
|
9
|
-
* import {
|
|
9
|
+
* import { getCsrfToken, csrfFetch } from 'litestar-vite-plugin/helpers'
|
|
10
10
|
*
|
|
11
|
-
* //
|
|
12
|
-
* const
|
|
11
|
+
* // Get CSRF token
|
|
12
|
+
* const token = getCsrfToken()
|
|
13
13
|
*
|
|
14
14
|
* // Make a fetch request with CSRF token
|
|
15
15
|
* await csrfFetch('/api/submit', {
|
|
@@ -18,7 +18,15 @@
|
|
|
18
18
|
* })
|
|
19
19
|
* ```
|
|
20
20
|
*
|
|
21
|
+
* For type-safe routing, import from your generated routes file:
|
|
22
|
+
* ```ts
|
|
23
|
+
* import { route, routes, type RouteName } from '@/generated/routes'
|
|
24
|
+
*
|
|
25
|
+
* // Type-safe URL generation
|
|
26
|
+
* const url = route('user_detail', { user_id: 123 }) // Compile-time checked!
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
21
29
|
* @module
|
|
22
30
|
*/
|
|
23
|
-
export {
|
|
24
|
-
export {
|
|
31
|
+
export { csrfFetch, csrfHeaders, getCsrfToken } from "./csrf.js";
|
|
32
|
+
export { addDirective, registerHtmxExtension, setDebug as setHtmxDebug, swapJson } from "./htmx.js";
|
package/dist/js/helpers/index.js
CHANGED
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @example
|
|
8
8
|
* ```ts
|
|
9
|
-
* import {
|
|
9
|
+
* import { getCsrfToken, csrfFetch } from 'litestar-vite-plugin/helpers'
|
|
10
10
|
*
|
|
11
|
-
* //
|
|
12
|
-
* const
|
|
11
|
+
* // Get CSRF token
|
|
12
|
+
* const token = getCsrfToken()
|
|
13
13
|
*
|
|
14
14
|
* // Make a fetch request with CSRF token
|
|
15
15
|
* await csrfFetch('/api/submit', {
|
|
@@ -18,9 +18,17 @@
|
|
|
18
18
|
* })
|
|
19
19
|
* ```
|
|
20
20
|
*
|
|
21
|
+
* For type-safe routing, import from your generated routes file:
|
|
22
|
+
* ```ts
|
|
23
|
+
* import { route, routes, type RouteName } from '@/generated/routes'
|
|
24
|
+
*
|
|
25
|
+
* // Type-safe URL generation
|
|
26
|
+
* const url = route('user_detail', { user_id: 123 }) // Compile-time checked!
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
21
29
|
* @module
|
|
22
30
|
*/
|
|
23
31
|
// CSRF utilities
|
|
24
|
-
export {
|
|
25
|
-
//
|
|
26
|
-
export {
|
|
32
|
+
export { csrfFetch, csrfHeaders, getCsrfToken } from "./csrf.js";
|
|
33
|
+
// HTMX utilities
|
|
34
|
+
export { addDirective, registerHtmxExtension, setDebug as setHtmxDebug, swapJson } from "./htmx.js";
|
package/dist/js/index.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { type ConfigEnv, type Plugin, type UserConfig } from "vite";
|
|
2
1
|
import { type Config as FullReloadConfig } from "vite-plugin-full-reload";
|
|
3
2
|
/**
|
|
4
3
|
* Configuration for TypeScript type generation.
|
|
@@ -74,6 +73,13 @@ export interface PluginConfig {
|
|
|
74
73
|
* @default 'public/dist'
|
|
75
74
|
*/
|
|
76
75
|
bundleDirectory?: string;
|
|
76
|
+
/**
|
|
77
|
+
* Vite's public directory for static, unprocessed assets.
|
|
78
|
+
* Mirrors Vite's `publicDir` option.
|
|
79
|
+
*
|
|
80
|
+
* @default 'public'
|
|
81
|
+
*/
|
|
82
|
+
publicDir?: string;
|
|
77
83
|
/**
|
|
78
84
|
* Litestar's public assets directory. These are the assets that Vite will serve when developing.
|
|
79
85
|
*
|
|
@@ -122,18 +128,36 @@ export interface PluginConfig {
|
|
|
122
128
|
/**
|
|
123
129
|
* Enable and configure TypeScript type generation.
|
|
124
130
|
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
131
|
+
* Configuration priority (highest to lowest):
|
|
132
|
+
* 1. Explicit vite.config.ts value - ALWAYS wins
|
|
133
|
+
* 2. .litestar.json value - used if no explicit config
|
|
134
|
+
* 3. Hardcoded defaults - fallback if nothing else
|
|
135
|
+
*
|
|
136
|
+
* When set to `"auto"` (recommended): reads all config from `.litestar.json`.
|
|
137
|
+
* If `.litestar.json` is missing, type generation is disabled.
|
|
138
|
+
*
|
|
139
|
+
* When set to `true`: enables type generation with hardcoded defaults.
|
|
140
|
+
* When set to `false`: disables type generation entirely.
|
|
141
|
+
* When set to a TypesConfig object: uses your explicit settings.
|
|
127
142
|
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
143
|
+
* When not specified (undefined): behaves like `"auto"` - reads from
|
|
144
|
+
* `.litestar.json` if present, otherwise disabled.
|
|
130
145
|
*
|
|
131
146
|
* @example
|
|
132
147
|
* ```ts
|
|
133
|
-
* //
|
|
148
|
+
* // Recommended: auto-read from .litestar.json (simplest)
|
|
149
|
+
* litestar({ input: 'src/main.ts' })
|
|
150
|
+
*
|
|
151
|
+
* // Explicit auto mode
|
|
152
|
+
* litestar({ input: 'src/main.ts', types: 'auto' })
|
|
153
|
+
*
|
|
154
|
+
* // Force enable with hardcoded defaults (ignores .litestar.json)
|
|
134
155
|
* litestar({ input: 'src/main.ts', types: true })
|
|
135
156
|
*
|
|
136
|
-
* //
|
|
157
|
+
* // Force disable
|
|
158
|
+
* litestar({ input: 'src/main.ts', types: false })
|
|
159
|
+
*
|
|
160
|
+
* // Manual override (ignores .litestar.json for types)
|
|
137
161
|
* litestar({
|
|
138
162
|
* input: 'src/main.ts',
|
|
139
163
|
* types: {
|
|
@@ -144,9 +168,9 @@ export interface PluginConfig {
|
|
|
144
168
|
* })
|
|
145
169
|
* ```
|
|
146
170
|
*
|
|
147
|
-
* @default
|
|
171
|
+
* @default undefined (auto-detect from .litestar.json)
|
|
148
172
|
*/
|
|
149
|
-
types?: boolean | TypesConfig;
|
|
173
|
+
types?: boolean | "auto" | TypesConfig;
|
|
150
174
|
/**
|
|
151
175
|
* JavaScript runtime executor for package commands.
|
|
152
176
|
* Used when running tools like @hey-api/openapi-ts.
|
|
@@ -162,15 +186,13 @@ interface RefreshConfig {
|
|
|
162
186
|
paths: string[];
|
|
163
187
|
config?: FullReloadConfig;
|
|
164
188
|
}
|
|
165
|
-
interface LitestarPlugin extends Plugin {
|
|
166
|
-
config: (config: UserConfig, env: ConfigEnv) => UserConfig;
|
|
167
|
-
}
|
|
168
189
|
type DevServerUrl = `${"http" | "https"}://${string}:${number}`;
|
|
169
190
|
export declare const refreshPaths: string[];
|
|
170
191
|
/**
|
|
171
192
|
* Litestar plugin for Vite.
|
|
172
193
|
*
|
|
173
194
|
* @param config - A config object or relative path(s) of the scripts to be compiled.
|
|
195
|
+
* @returns An array of Vite plugins. Return type is `any[]` to avoid cross-version type conflicts.
|
|
174
196
|
*/
|
|
175
|
-
export default function litestar(config: string | string[] | PluginConfig): [
|
|
197
|
+
export default function litestar(config: string | string[] | PluginConfig): any[];
|
|
176
198
|
export {};
|