yaxa-svelte 2.6.0 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/README.md +136 -25
  2. package/dist/auth/index.d.ts +7 -1
  3. package/dist/auth/index.js +3 -0
  4. package/dist/client/index.d.ts +3 -0
  5. package/dist/client/index.js +3 -0
  6. package/dist/client/mutation.svelte.d.ts +37 -0
  7. package/dist/client/mutation.svelte.js +87 -0
  8. package/dist/client/query-cache.svelte.d.ts +51 -0
  9. package/dist/client/query-cache.svelte.js +172 -0
  10. package/dist/client/query.svelte.d.ts +71 -0
  11. package/dist/client/query.svelte.js +213 -0
  12. package/dist/client/query.test.d.ts +1 -0
  13. package/dist/client/query.test.js +242 -0
  14. package/dist/components/ai/ai.test.js +68 -1
  15. package/dist/components/ai/index.d.ts +2 -0
  16. package/dist/components/ai/index.js +1 -0
  17. package/dist/components/ai/useAiChat.svelte.d.ts +68 -0
  18. package/dist/components/ai/useAiChat.svelte.js +258 -0
  19. package/dist/components/blocks/FAQ.svelte +11 -0
  20. package/dist/components/blocks/FAQ.svelte.d.ts +1 -0
  21. package/dist/components/blocks/FAQ.test.js +20 -0
  22. package/dist/components/saas/AuthCard.svelte +16 -12
  23. package/dist/components/saas/AuthCard.svelte.d.ts +3 -3
  24. package/dist/components/saas/ResetPasswordCard.svelte +13 -0
  25. package/dist/components/saas/ResetPasswordCard.svelte.d.ts +5 -0
  26. package/dist/components/saas/SignInCard.svelte +13 -0
  27. package/dist/components/saas/SignInCard.svelte.d.ts +5 -0
  28. package/dist/components/saas/SignUpCard.svelte +13 -0
  29. package/dist/components/saas/SignUpCard.svelte.d.ts +5 -0
  30. package/dist/components/saas/auth-cards.test.d.ts +1 -0
  31. package/dist/components/saas/auth-cards.test.js +42 -0
  32. package/dist/components/seo/Seo.svelte +16 -9
  33. package/dist/index.d.ts +23 -4
  34. package/dist/index.js +14 -3
  35. package/dist/server/ai/index.d.ts +2 -0
  36. package/dist/server/ai/index.js +1 -0
  37. package/dist/server/ai/stream.d.ts +24 -0
  38. package/dist/server/ai/stream.js +178 -0
  39. package/dist/server/ai/stream.test.d.ts +1 -0
  40. package/dist/server/ai/stream.test.js +83 -0
  41. package/dist/server/ai/types.d.ts +43 -0
  42. package/dist/server/ai/types.js +1 -0
  43. package/dist/server/index.d.ts +6 -0
  44. package/dist/server/index.js +4 -0
  45. package/dist/server/openapi/handler.d.ts +1 -1
  46. package/dist/server/openapi/handler.js +41 -41
  47. package/dist/server/openapi/openapi.test.js +6 -7
  48. package/dist/server/openapi/types.d.ts +2 -3
  49. package/dist/site/context.svelte.d.ts +3 -0
  50. package/dist/site/context.svelte.js +7 -0
  51. package/dist/site/hook.d.ts +5 -1
  52. package/dist/site/hook.js +11 -1
  53. package/dist/site/index.d.ts +26 -0
  54. package/dist/site/index.js +25 -0
  55. package/dist/site/llms.d.ts +27 -0
  56. package/dist/site/llms.js +58 -0
  57. package/dist/site/schema.d.ts +45 -0
  58. package/dist/site/schema.js +43 -0
  59. package/dist/site/seo-helpers.d.ts +1 -0
  60. package/dist/site/site-export.test.d.ts +1 -0
  61. package/dist/site/site-export.test.js +193 -0
  62. package/dist/site/skill.d.ts +26 -0
  63. package/dist/site/skill.js +69 -0
  64. package/dist/site/useSeo.svelte.d.ts +23 -0
  65. package/dist/site/useSeo.svelte.js +40 -0
  66. package/dist/site/useSeo.test.d.ts +1 -0
  67. package/dist/site/useSeo.test.js +35 -0
  68. package/package.json +5 -1
package/README.md CHANGED
@@ -5,6 +5,7 @@
5
5
  **The Intuitive Svelte UI & Solo SaaS Library**
6
6
  _Nuxt UI v4 & Nuxt UI Pro Equivalent for SvelteKit 2 & 3 & Svelte 5 with Built-in SEO & SaaS Parity._
7
7
 
8
+ [![Live Demo](https://img.shields.io/badge/Live_Demo-yaxa.vercel.app-000000?style=flat&logo=vercel&logoColor=white)](https://yaxa.vercel.app)
8
9
  [![npm version](https://img.shields.io/npm/v/yaxa-svelte.svg?color=CB3837&logo=npm)](https://www.npmjs.com/package/yaxa-svelte)
9
10
  [![npm downloads](https://img.shields.io/npm/dm/yaxa-svelte.svg?color=blue)](https://www.npmjs.com/package/yaxa-svelte)
10
11
  [![Svelte 5](https://img.shields.io/badge/Svelte-5.0+-FF3E00?style=flat&logo=svelte&logoColor=white)](https://svelte.dev)
@@ -15,7 +16,13 @@ _Nuxt UI v4 & Nuxt UI Pro Equivalent for SvelteKit 2 & 3 & Svelte 5 with Built-i
15
16
  [![Security Policy](https://img.shields.io/badge/Security-Policy-blue.svg)](SECURITY.md)
16
17
  [![Contributing](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md)
17
18
 
18
- [**Live Documentation & Playground →**](https://yaxa.vercel.app)
19
+ <br />
20
+
21
+ [**🚀 Live Demo**](https://yaxa.vercel.app) &nbsp;•&nbsp;
22
+ [**📚 Documentation**](https://yaxa.vercel.app/docs) &nbsp;•&nbsp;
23
+ [**🎨 Theme Studio**](https://yaxa.vercel.app/theme) &nbsp;•&nbsp;
24
+ [**💼 SaaS Showcase**](https://yaxa.vercel.app/saas) &nbsp;•&nbsp;
25
+ [**⚡ Scalar API Reference**](https://yaxa.vercel.app/api/docs)
19
26
 
20
27
  </div>
21
28
 
@@ -163,7 +170,7 @@ Handle **all 5 SEO endpoints** and **Better-Auth session parsing** in `src/hooks
163
170
  ```ts
164
171
  // src/hooks.server.ts
165
172
  import { sequence } from '@sveltejs/kit/hooks';
166
- import { createYaxaHook } from 'yaxa-svelte/server';
173
+ import { createYaxaHook } from 'yaxa-svelte/seo';
167
174
  import { createYaxaAuth, createYaxaAuthHook } from 'yaxa-svelte/auth';
168
175
  import { siteConfig } from './site.config';
169
176
 
@@ -178,6 +185,10 @@ const authHook = createYaxaAuthHook({
178
185
  export const handle = sequence(yaxaHook, authHook);
179
186
  ```
180
187
 
188
+ > **Pure Static Sites (`@sveltejs/adapter-static`):** Import directly from `yaxa-svelte/seo` (`createSitemapHandler`, `createRobotsHandler`, `createOgImageHandler`, `generateSitemapXsl`) with `export const prerender = true`. Completely headless with **zero DB, auth, or server runtime peer dependencies**.
189
+
190
+ ````
191
+
181
192
  ---
182
193
 
183
194
  ## 🛡️ Declarative Feature Gating (`<Gate />` & `useGate()`)
@@ -186,16 +197,16 @@ Declarative and programmatic subscription tier and RBAC gating with automatic Sv
186
197
 
187
198
  ```svelte
188
199
  <script lang="ts">
189
- import { Gate, useGate } from 'yaxa-svelte';
200
+ import { Gate, useGate } from 'yaxa-svelte';
190
201
 
191
- const gate = useGate();
202
+ const gate = useGate();
192
203
  </script>
193
204
 
194
205
  <!-- 0 props needed when wrapped in <YaxaApp user={data.user}> -->
195
206
  <Gate requiredPlan="pro" preview={true}>
196
- <AdvancedAnalyticsWidget />
207
+ <AdvancedAnalyticsWidget />
197
208
  </Gate>
198
- ```
209
+ ````
199
210
 
200
211
  ---
201
212
 
@@ -276,7 +287,7 @@ export const POST = yaxaEndpoint({
276
287
 
277
288
  ## 📜 Auto-Generated OpenAPI 3.1 & Interactive Docs (`/api/docs`)
278
289
 
279
- Instantly generate an OpenAPI 3.1 specification and host interactive documentation ([Scalar](https://scalar.com/), Swagger UI, or Redoc) directly from your `yaxaEndpoint` routes:
290
+ Instantly generate an OpenAPI 3.1 specification and host interactive documentation ([Scalar](https://scalar.com/) or Swagger UI) directly from your `yaxaEndpoint` routes:
280
291
 
281
292
  ```ts
282
293
  // src/routes/api/docs/+server.ts
@@ -286,7 +297,7 @@ import { POST as createProject } from '../projects/+server';
286
297
  export const GET = createApiDocsHandler({
287
298
  title: 'Acme SaaS API',
288
299
  version: '1.0.0',
289
- theme: 'scalar', // 'scalar' (default) | 'swagger' | 'redoc'
300
+ theme: 'scalar', // 'scalar' (default) | 'swagger'
290
301
  endpoints: {
291
302
  '/api/projects': {
292
303
  POST: createProject
@@ -295,7 +306,7 @@ export const GET = createApiDocsHandler({
295
306
  });
296
307
  ```
297
308
 
298
- - **Interactive Reference UI:** Open `/api/docs` in your browser (supports `?theme=scalar` (default), `?theme=swagger`, or `?theme=redoc`).
309
+ - **Interactive Reference UI:** Open `/api/docs` in your browser (supports `?theme=scalar` (default) or `?theme=swagger`).
299
310
  - **Live Demo:** Try it live on [yaxa.vercel.app/api/docs](https://yaxa.vercel.app/api/docs).
300
311
  - **Raw OpenAPI 3.1 JSON:** Request `/api/docs?format=json` or send `Accept: application/json` for SDK generation.
301
312
 
@@ -392,6 +403,46 @@ await broadcast(`org:${orgId}`, 'invoice.settled', {
392
403
 
393
404
  ---
394
405
 
406
+ ## 🤖 AI Streaming Bridge & Runes Chat (`useAiChat` & `createAiStreamHandler`)
407
+
408
+ Zero-boilerplate bridge connecting SvelteKit streaming endpoints directly to `<AiChat>`, with progressive text streaming, thought duration/step inspection, tool invocation cards, and abortable retries:
409
+
410
+ ```ts
411
+ // 1. Server Streaming Endpoint (src/routes/api/ai/chat/+server.ts)
412
+ import { createAiStreamHandler, requireAuth } from 'yaxa-svelte/server';
413
+
414
+ export const POST = createAiStreamHandler({
415
+ guards: [requireAuth()],
416
+ // Built-in realistic mock streaming for instant zero-config dev & demos,
417
+ // or provide your custom handler with Google Gemini, Anthropic, or OpenAI:
418
+ handler: async function* ({ messages }) {
419
+ yield { type: 'thought', thought: { running: true, text: 'Analyzing user request...' } };
420
+ // Yield text deltas, thoughts, and tool call states
421
+ yield { type: 'text', delta: 'Hello from your AI backend!' };
422
+ yield { type: 'done' };
423
+ }
424
+ });
425
+ ```
426
+
427
+ ```svelte
428
+ <!-- 2. Svelte 5 Client Chat (+page.svelte) -->
429
+ <script lang="ts">
430
+ import { AiChat, useAiChat } from 'yaxa-svelte';
431
+
432
+ const chat = useAiChat({ api: '/api/ai/chat' });
433
+ </script>
434
+
435
+ <AiChat
436
+ messages={chat.messages}
437
+ loading={chat.loading}
438
+ onsend={(text) => chat.send(text)}
439
+ onstop={() => chat.stop()}
440
+ onretry={(msg) => chat.retry(msg)}
441
+ />
442
+ ```
443
+
444
+ ---
445
+
395
446
  ## 🔗 End-to-End Type-Safe Client RPC (`createApiClient`)
396
447
 
397
448
  Zero-codegen end-to-end type safety bridging your server `yaxaEndpoint` routes directly into Svelte 5 components and universal `load` functions. Adds less than 500 bytes gzipped to your client bundle:
@@ -441,22 +492,82 @@ export const load = async ({ fetch }) => {
441
492
 
442
493
  ---
443
494
 
495
+ ## ⚡ Runes-Native Reactive Query & Realtime RPC (`createQuery`, `createMutation`)
496
+
497
+ A zero-overhead, runes-native query and mutation cache built specifically for Svelte 5. Features request coalescing, stale-while-revalidate, window focus revalidation, optimistic updates with automatic rollback, and live SSE cache invalidation:
498
+
499
+ ```svelte
500
+ <!-- src/routes/projects/+page.svelte -->
501
+ <script lang="ts">
502
+ import { api } from '$lib/api';
503
+ import { createQuery, createMutation } from 'yaxa-svelte';
504
+
505
+ let activeOrg = $state('org_123');
506
+
507
+ // 1. Reactive Query: auto-refetches when activeOrg changes
508
+ const projects = createQuery(() => api['/api/projects'].get({ query: { orgId: activeOrg } }), {
509
+ staleTimeMs: 15_000,
510
+ refetchOnWindowFocus: true,
511
+ subscribeToSse: `org:${activeOrg}/projects` // ⚡ Live SSE invalidation
512
+ });
513
+
514
+ // 2. Optimistic Mutation with Automatic Rollback on RFC 7807 Error
515
+ const createProject = createMutation(
516
+ (name: string) => api['/api/projects'].post({ body: { name, orgId: activeOrg } }),
517
+ {
518
+ onMutate: (name) => {
519
+ const previous = projects.data;
520
+ projects.optimisticUpdate((curr) => [
521
+ { id: 'temp-' + Date.now(), name, status: 'creating' },
522
+ ...(curr ?? [])
523
+ ]);
524
+ return { previous }; // Snapshot for rollback
525
+ },
526
+ onError: (err, _name, context) => {
527
+ if (context?.previous) projects.setData(context.previous);
528
+ toast.error(err.title, err.detail);
529
+ },
530
+ onSettled: () => projects.invalidate()
531
+ }
532
+ );
533
+ </script>
534
+
535
+ {#if projects.isLoading}
536
+ <Skeleton count={3} class="h-10 w-full" />
537
+ {:else if projects.data}
538
+ <ul>
539
+ {#each projects.data as project}
540
+ <li class={project.id.startsWith('temp-') ? 'opacity-50' : ''}>
541
+ {project.name}
542
+ </li>
543
+ {/each}
544
+ </ul>
545
+ {/if}
546
+
547
+ <Button onclick={() => createProject.mutate('New Project')} loading={createProject.isPending}>
548
+ Create Project
549
+ </Button>
550
+ ```
551
+
552
+ ---
553
+
444
554
  ## 📦 Subpath Exports Architecture
445
555
 
446
- | Subpath | Description |
447
- | :---------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
448
- | `yaxa-svelte` | Core UI Primitives, SEO Components, `<Gate />`, Type-Safe Client RPC (`createApiClient`), Theme System & Composables (`useGate`, `useUpload`, `useSSE`, `useToast`, `useClipboard`, etc.) |
449
- | `yaxa-svelte/admin` | Drizzle ORM Schema Introspection, `<AdminDashboard />`, `createYaxaAdminHook`, `createDrizzleAdmin`, `<DevSandbox />`, `<RecordDrawer />` |
450
- | `yaxa-svelte/server` | `yaxaEndpoint` Pipeline, Declarative Guards, OpenAPI 3.1 & Scalar Docs, RFC 7807, Real-Time SSE (`yaxaSSE`, `broadcast`), Background Jobs (`enqueueJob`, `createJobRunner`), `eventBus`, Server Hooks, DB, Storage & Email |
451
- | `yaxa-svelte/auth` | Better-Auth client & server integration (`useAuth`, `<AuthCard />`, `<PasskeyUI />`, `<TwoFactorModal />`, `createYaxaAuth`) |
452
- | `yaxa-svelte/db` | Multi-dialect Drizzle ORM clients & schemas for Neon (PostgreSQL) and Turso (SQLite/LibSQL) |
453
- | `yaxa-svelte/polar` | Polar.sh billing, customer portal sessions, and webhook synchronization |
454
- | `yaxa-svelte/email` | Resend transactional email templates with automatic development console fallback |
455
- | `yaxa-svelte/storage` | AWS S3 and Cloudflare R2 presigned upload URL generators |
456
- | `yaxa-svelte/testing` | Test mocks & utilities (`createMockSiteConfig`, `createMockUser`, `createTestWrapperProps`) |
457
- | `yaxa-svelte/vite` | Turnkey Vite plugin (`yaxa()`) |
458
- | `yaxa-svelte/yaxa.css` | Standalone turnkey Tailwind CSS v4 stylesheet with base styling, `@theme`, and scrollbars |
459
- | `yaxa-svelte/theme.css` | Pure Tailwind CSS v4 design tokens, `:root` variables, and harmonic radius scale without `@import 'tailwindcss'` |
556
+ | Subpath | Description |
557
+ | :---------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
558
+ | `yaxa-svelte` | Core UI Primitives, SEO Components, `<Gate />`, Type-Safe Client RPC (`createApiClient`), Runes Query Engine (`createQuery`, `createMutation`, `QueryClient`), Theme System & Composables (`useGate`, `useUpload`, `useSSE`, `useToast`, `useClipboard`, etc.) |
559
+ | `yaxa-svelte/seo` | Standalone SEO & Agent Engine: Sitemaps, robots.txt, XSL stylesheets, OpenGraph SVG, Schema.org JSON-LD (FAQPage, Product), WebManifest, llms.txt, skill.md, `useSeo()` runes, and prerendering utilities (Zero DB/Auth dependencies; 100% compatible with `@sveltejs/adapter-static`) |
560
+ | `yaxa-svelte/admin` | Drizzle ORM Schema Introspection, `<AdminDashboard />`, `createYaxaAdminHook`, `createDrizzleAdmin`, `<DevSandbox />`, `<RecordDrawer />` |
561
+ | `yaxa-svelte/server` | `yaxaEndpoint` Pipeline, Declarative Guards, OpenAPI 3.1 & Scalar Docs, RFC 7807, Real-Time SSE (`yaxaSSE`, `broadcast`), AI Streaming (`createAiStreamHandler`), Background Jobs (`enqueueJob`, `createJobRunner`), `eventBus`, Server Hooks, DB, Storage & Email |
562
+ | `yaxa-svelte/auth` | Better-Auth client & server integration (`useAuth`, `<SignInCard />`, `<SignUpCard />`, `<ResetPasswordCard />`, `<AuthCard />`, `<PasskeyUI />`, `<TwoFactorModal />`, `createYaxaAuth`) |
563
+ | `yaxa-svelte/db` | Multi-dialect Drizzle ORM clients & schemas for Neon (PostgreSQL) and Turso (SQLite/LibSQL) |
564
+ | `yaxa-svelte/polar` | Polar.sh billing, customer portal sessions, and webhook synchronization |
565
+ | `yaxa-svelte/email` | Resend transactional email templates with automatic development console fallback |
566
+ | `yaxa-svelte/storage` | AWS S3 and Cloudflare R2 presigned upload URL generators |
567
+ | `yaxa-svelte/testing` | Test mocks & utilities (`createMockSiteConfig`, `createMockUser`, `createTestWrapperProps`) |
568
+ | `yaxa-svelte/vite` | Turnkey Vite plugin (`yaxa()`) |
569
+ | `yaxa-svelte/yaxa.css` | Standalone turnkey Tailwind CSS v4 stylesheet with base styling, `@theme`, and scrollbars |
570
+ | `yaxa-svelte/theme.css` | Pure Tailwind CSS v4 design tokens, `:root` variables, and harmonic radius scale without `@import 'tailwindcss'` |
460
571
 
461
572
  ---
462
573
 
@@ -465,9 +576,9 @@ export const load = async ({ fetch }) => {
465
576
  | Category | Components |
466
577
  | :--------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
467
578
  | **Theming & Studio** | `<ThemeStudio>`, `theme`, `ACCENT_PALETTES`, `NEUTRAL_PALETTES`, `RADIUS_PRESETS`, Harmonic Radius Multiplier Scale, Chart Palette Series Tokens |
468
- | **AI Primitives** | `<AiChat>`, `<PromptBar>`, `<AiThought>`, `<AiToolCall>` |
579
+ | **AI Primitives** | `<AiChat>`, `<PromptBar>`, `<AiThought>`, `<AiToolCall>`, `useAiChat` |
469
580
  | **Charts (Pure SVG)** | `<LineChart>`, `<BarChart>`, `<DonutChart>` (0 external charting dependencies, auto-responsive, dark/light theme aware) |
470
- | **SaaS & Auth Suite** | `<PasskeyUI>` (WebAuthn), `<TwoFactorModal>` (TOTP), `<AuthCard>` (Polymorphic), `<Gate>`, `<UserMenu>`, `<OrgSwitcher>`, `<PricingCard>`, `<PricingTable>`, `<SubscriptionCard>`, `useAuth`, `useGate`, `useUpload` |
581
+ | **SaaS & Auth Suite** | `<SignInCard>`, `<SignUpCard>`, `<ResetPasswordCard>`, `<PasskeyUI>` (WebAuthn), `<TwoFactorModal>` (TOTP), `<AuthCard>` (Polymorphic), `<Gate>`, `<UserMenu>`, `<OrgSwitcher>`, `<PricingCard>`, `<PricingTable>`, `<SubscriptionCard>`, `useAuth`, `useGate`, `useUpload` |
471
582
  | **Admin Suite** | `<AdminDashboard>`, `<RecordDrawer>`, `<DevSandbox>`, `<ImpersonationBanner>`, `createYaxaAdminHook`, `createDrizzleAdmin` |
472
583
  | **Elements & Code** | `<CodeDiff>` (Interactive Diff & Changelog), `<CodeBlock>`, `<DataTable>` (Client & Server Modes), `Button`, `ButtonGroup`, `Badge`, `Avatar`, `AvatarGroup`, `Chip`, `Meter`, `MetricCard`, `Kbd`, `Icon`, `Spinner`, `Progress`, `Skeleton`, `Link`, `Logo`, `Tree`, `Timeline`, `Terminal`, `VirtualList`, `Carousel` |
473
584
  | **Forms (Auto-Wired)** | `<Form>`, `<FormField>`, `Input`, `InputOTP`, `Textarea`, `Checkbox`, `Switch`, `Select`, `Combobox`, `MultiSelect`, `NumberInput`, `PhoneInput`, `CreditCardInput`, `RadioGroup`, `Slider`, `ColorPicker`, `ToggleGroup`, `DatePicker`, `DateRangePicker`, `Dropzone`, `<RichTextEditor>` |
@@ -1,5 +1,11 @@
1
1
  export { default as AuthCard } from '../components/saas/AuthCard.svelte';
2
- export type { AuthCardMode } from '../components/saas/AuthCard.svelte';
2
+ export type { AuthCardProps, AuthCardMode } from '../components/saas/AuthCard.svelte';
3
+ export { default as SignInCard } from '../components/saas/SignInCard.svelte';
4
+ export type { SignInCardProps } from '../components/saas/SignInCard.svelte';
5
+ export { default as SignUpCard } from '../components/saas/SignUpCard.svelte';
6
+ export type { SignUpCardProps } from '../components/saas/SignUpCard.svelte';
7
+ export { default as ResetPasswordCard } from '../components/saas/ResetPasswordCard.svelte';
8
+ export type { ResetPasswordCardProps } from '../components/saas/ResetPasswordCard.svelte';
3
9
  export { default as PasskeyUI, passkeyUiVariants } from '../components/saas/PasskeyUI.svelte';
4
10
  export type { PasskeyUiProps } from '../components/saas/PasskeyUI.svelte';
5
11
  export { default as TwoFactorModal } from '../components/saas/TwoFactorModal.svelte';
@@ -1,5 +1,8 @@
1
1
  // Yaxa SaaS Auth Suite (Better-Auth integration for Svelte 5 & SvelteKit)
2
2
  export { default as AuthCard } from '../components/saas/AuthCard.svelte';
3
+ export { default as SignInCard } from '../components/saas/SignInCard.svelte';
4
+ export { default as SignUpCard } from '../components/saas/SignUpCard.svelte';
5
+ export { default as ResetPasswordCard } from '../components/saas/ResetPasswordCard.svelte';
3
6
  export { default as PasskeyUI, passkeyUiVariants } from '../components/saas/PasskeyUI.svelte';
4
7
  export { default as TwoFactorModal } from '../components/saas/TwoFactorModal.svelte';
5
8
  export { useAuth } from '../composables/useAuth.svelte';
@@ -1,2 +1,5 @@
1
1
  export * from './types';
2
2
  export * from './client';
3
+ export * from './query-cache.svelte';
4
+ export * from './query.svelte';
5
+ export * from './mutation.svelte';
@@ -1,2 +1,5 @@
1
1
  export * from './types';
2
2
  export * from './client';
3
+ export * from './query-cache.svelte';
4
+ export * from './query.svelte';
5
+ export * from './mutation.svelte';
@@ -0,0 +1,37 @@
1
+ import type { ClientResponse, ProblemDetails } from './types';
2
+ export type MutationFn<TData = any, TVariables = any> = (variables: TVariables) => Promise<ClientResponse<TData> | TData>;
3
+ export interface CreateMutationOptions<TData = any, TVariables = any, TError = ProblemDetails | Error, TContext = unknown> {
4
+ /**
5
+ * Callback fired immediately before the mutation runs.
6
+ * Ideal for optimistic updates; returned context will be passed to onError and onSettled.
7
+ */
8
+ onMutate?: (variables: TVariables) => Promise<TContext> | TContext;
9
+ /**
10
+ * Callback fired when mutation succeeds.
11
+ */
12
+ onSuccess?: (data: TData, variables: TVariables, context: TContext | undefined) => Promise<void> | void;
13
+ /**
14
+ * Callback fired when mutation fails.
15
+ */
16
+ onError?: (error: TError, variables: TVariables, context: TContext | undefined) => Promise<void> | void;
17
+ /**
18
+ * Callback fired when mutation completes (either success or error).
19
+ */
20
+ onSettled?: (data: TData | null, error: TError | null, variables: TVariables, context: TContext | undefined) => Promise<void> | void;
21
+ }
22
+ export interface CreateMutationResult<TData = any, TVariables = any, TError = ProblemDetails | Error> {
23
+ readonly data: TData | null;
24
+ readonly error: TError | null;
25
+ readonly status: 'idle' | 'pending' | 'success' | 'error';
26
+ readonly isPending: boolean;
27
+ readonly isSuccess: boolean;
28
+ readonly isError: boolean;
29
+ readonly isIdle: boolean;
30
+ mutate: (variables: TVariables) => Promise<TData>;
31
+ reset: () => void;
32
+ }
33
+ /**
34
+ * Idiomatic Svelte 5 Runes Mutation Composable.
35
+ * Features optimistic rollbacks, lifecycle callbacks, and RFC 7807 problem details unwrapping.
36
+ */
37
+ export declare function createMutation<TData = any, TVariables = void, TError = ProblemDetails | Error, TContext = unknown>(mutationFn: MutationFn<TData, TVariables>, options?: CreateMutationOptions<TData, TVariables, TError, TContext>): CreateMutationResult<TData, TVariables, TError>;
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Checks if a response is a Yaxa ClientResponse.
3
+ */
4
+ function isClientResponse(res) {
5
+ return (typeof res === 'object' &&
6
+ res !== null &&
7
+ 'ok' in res &&
8
+ 'status' in res &&
9
+ ('data' in res || 'error' in res));
10
+ }
11
+ /**
12
+ * Idiomatic Svelte 5 Runes Mutation Composable.
13
+ * Features optimistic rollbacks, lifecycle callbacks, and RFC 7807 problem details unwrapping.
14
+ */
15
+ export function createMutation(mutationFn, options = {}) {
16
+ let data = $state(null);
17
+ let error = $state(null);
18
+ let status = $state('idle');
19
+ const isPending = $derived(status === 'pending');
20
+ const isSuccess = $derived(status === 'success');
21
+ const isError = $derived(status === 'error');
22
+ const isIdle = $derived(status === 'idle');
23
+ async function mutate(variables) {
24
+ status = 'pending';
25
+ error = null;
26
+ let context;
27
+ try {
28
+ if (options.onMutate) {
29
+ context = await options.onMutate(variables);
30
+ }
31
+ const result = await mutationFn(variables);
32
+ let finalData;
33
+ if (isClientResponse(result)) {
34
+ if (!result.ok) {
35
+ throw result.error;
36
+ }
37
+ finalData = result.data;
38
+ }
39
+ else {
40
+ finalData = result;
41
+ }
42
+ data = finalData;
43
+ status = 'success';
44
+ await options.onSuccess?.(finalData, variables, context);
45
+ await options.onSettled?.(finalData, null, variables, context);
46
+ return finalData;
47
+ }
48
+ catch (err) {
49
+ const errPayload = err;
50
+ error = errPayload;
51
+ status = 'error';
52
+ await options.onError?.(errPayload, variables, context);
53
+ await options.onSettled?.(null, errPayload, variables, context);
54
+ throw err;
55
+ }
56
+ }
57
+ function reset() {
58
+ data = null;
59
+ error = null;
60
+ status = 'idle';
61
+ }
62
+ return {
63
+ get data() {
64
+ return data;
65
+ },
66
+ get error() {
67
+ return error;
68
+ },
69
+ get status() {
70
+ return status;
71
+ },
72
+ get isPending() {
73
+ return isPending;
74
+ },
75
+ get isSuccess() {
76
+ return isSuccess;
77
+ },
78
+ get isError() {
79
+ return isError;
80
+ },
81
+ get isIdle() {
82
+ return isIdle;
83
+ },
84
+ mutate,
85
+ reset
86
+ };
87
+ }
@@ -0,0 +1,51 @@
1
+ import { SvelteSet } from 'svelte/reactivity';
2
+ export type QueryKey = string | readonly unknown[];
3
+ /**
4
+ * Deterministically serializes a QueryKey into a cache string.
5
+ */
6
+ export declare function hashQueryKey(key: QueryKey): string;
7
+ export type QueryStatus = 'idle' | 'loading' | 'success' | 'error';
8
+ export interface QueryCacheEntry<TData = any, TError = any> {
9
+ readonly key: string;
10
+ data: TData | null;
11
+ error: TError | null;
12
+ status: QueryStatus;
13
+ isFetching: boolean;
14
+ updatedAt: number;
15
+ inFlightPromise: Promise<TData | null> | null;
16
+ subscribers: SvelteSet<() => void>;
17
+ invalidate: () => Promise<void>;
18
+ }
19
+ export interface InvalidateOptions {
20
+ key?: QueryKey;
21
+ exact?: boolean;
22
+ }
23
+ /**
24
+ * Runes-native Query Client managing cache entries, request coalescing,
25
+ * and real-time invalidation.
26
+ */
27
+ export declare class QueryClient {
28
+ private entries;
29
+ /**
30
+ * Retrieves or initializes a cache entry for a given key string.
31
+ */
32
+ getOrCreateEntry<TData = any, TError = any>(keyHash: string, initialData?: TData): QueryCacheEntry<TData, TError>;
33
+ /**
34
+ * Retrieves current data for a query key from the cache.
35
+ */
36
+ getQueryData<TData = any>(key: QueryKey): TData | null;
37
+ /**
38
+ * Manually sets or optimistically updates data for a query key.
39
+ */
40
+ setQueryData<TData = any>(key: QueryKey, updater: TData | ((previous: TData | null) => TData)): TData;
41
+ /**
42
+ * Invalidates queries matching a key or filter, triggering active subscribers to refetch.
43
+ */
44
+ invalidateQueries(filter?: InvalidateOptions | QueryKey): Promise<void>;
45
+ /**
46
+ * Clears the cache completely.
47
+ */
48
+ clear(): void;
49
+ }
50
+ export declare function getQueryClient(): QueryClient;
51
+ export declare function setQueryClient(client: QueryClient): void;
@@ -0,0 +1,172 @@
1
+ import { SvelteMap, SvelteSet } from 'svelte/reactivity';
2
+ /**
3
+ * Deterministically serializes a QueryKey into a cache string.
4
+ */
5
+ export function hashQueryKey(key) {
6
+ if (typeof key === 'string')
7
+ return key;
8
+ try {
9
+ return JSON.stringify(key, (_, val) => {
10
+ if (typeof val === 'object' && val !== null && !Array.isArray(val)) {
11
+ // Sort object keys deterministically
12
+ return Object.keys(val)
13
+ .sort()
14
+ .reduce((acc, k) => {
15
+ acc[k] = val[k];
16
+ return acc;
17
+ }, {});
18
+ }
19
+ return val;
20
+ });
21
+ }
22
+ catch {
23
+ return String(key);
24
+ }
25
+ }
26
+ /**
27
+ * Runes-native Query Client managing cache entries, request coalescing,
28
+ * and real-time invalidation.
29
+ */
30
+ export class QueryClient {
31
+ entries = new SvelteMap();
32
+ /**
33
+ * Retrieves or initializes a cache entry for a given key string.
34
+ */
35
+ getOrCreateEntry(keyHash, initialData) {
36
+ const entry = this.entries.get(keyHash);
37
+ if (!entry) {
38
+ let data = $state(initialData !== undefined ? initialData : null);
39
+ let error = $state(null);
40
+ let status = $state(initialData !== undefined ? 'success' : 'idle');
41
+ let isFetching = $state(false);
42
+ let updatedAt = $state(initialData !== undefined ? Date.now() : 0);
43
+ const subscribers = new SvelteSet();
44
+ const newEntry = {
45
+ key: keyHash,
46
+ get data() {
47
+ return data;
48
+ },
49
+ set data(val) {
50
+ data = val;
51
+ },
52
+ get error() {
53
+ return error;
54
+ },
55
+ set error(val) {
56
+ error = val;
57
+ },
58
+ get status() {
59
+ return status;
60
+ },
61
+ set status(val) {
62
+ status = val;
63
+ },
64
+ get isFetching() {
65
+ return isFetching;
66
+ },
67
+ set isFetching(val) {
68
+ isFetching = val;
69
+ },
70
+ get updatedAt() {
71
+ return updatedAt;
72
+ },
73
+ set updatedAt(val) {
74
+ updatedAt = val;
75
+ },
76
+ inFlightPromise: null,
77
+ subscribers,
78
+ invalidate: async () => {
79
+ for (const notify of Array.from(subscribers)) {
80
+ try {
81
+ notify();
82
+ }
83
+ catch {
84
+ // Ignored
85
+ }
86
+ }
87
+ }
88
+ };
89
+ this.entries.set(keyHash, newEntry);
90
+ return newEntry;
91
+ }
92
+ // If entry exists but has no data and initialData was provided, populate it
93
+ if (entry.data === null && initialData !== undefined && entry.status === 'idle') {
94
+ entry.data = initialData;
95
+ entry.status = 'success';
96
+ entry.updatedAt = Date.now();
97
+ }
98
+ return entry;
99
+ }
100
+ /**
101
+ * Retrieves current data for a query key from the cache.
102
+ */
103
+ getQueryData(key) {
104
+ const hash = hashQueryKey(key);
105
+ const entry = this.entries.get(hash);
106
+ return entry ? entry.data : null;
107
+ }
108
+ /**
109
+ * Manually sets or optimistically updates data for a query key.
110
+ */
111
+ setQueryData(key, updater) {
112
+ const hash = hashQueryKey(key);
113
+ const entry = this.getOrCreateEntry(hash);
114
+ const nextData = typeof updater === 'function'
115
+ ? updater(entry.data)
116
+ : updater;
117
+ entry.data = nextData;
118
+ entry.error = null;
119
+ entry.status = 'success';
120
+ entry.updatedAt = Date.now();
121
+ return nextData;
122
+ }
123
+ /**
124
+ * Invalidates queries matching a key or filter, triggering active subscribers to refetch.
125
+ */
126
+ async invalidateQueries(filter) {
127
+ if (!filter) {
128
+ for (const entry of this.entries.values()) {
129
+ await entry.invalidate();
130
+ }
131
+ return;
132
+ }
133
+ const isKey = typeof filter === 'string' || Array.isArray(filter);
134
+ const key = isKey ? filter : filter.key;
135
+ const exact = isKey ? false : Boolean(filter.exact);
136
+ if (!key) {
137
+ for (const entry of this.entries.values()) {
138
+ await entry.invalidate();
139
+ }
140
+ return;
141
+ }
142
+ const targetHash = hashQueryKey(key);
143
+ for (const [hash, entry] of this.entries.entries()) {
144
+ if (exact) {
145
+ if (hash === targetHash) {
146
+ await entry.invalidate();
147
+ }
148
+ }
149
+ else {
150
+ if (hash.startsWith(targetHash) || hash === targetHash) {
151
+ await entry.invalidate();
152
+ }
153
+ }
154
+ }
155
+ }
156
+ /**
157
+ * Clears the cache completely.
158
+ */
159
+ clear() {
160
+ this.entries.clear();
161
+ }
162
+ }
163
+ /**
164
+ * Global default query client singleton.
165
+ */
166
+ let globalClient = new QueryClient();
167
+ export function getQueryClient() {
168
+ return globalClient;
169
+ }
170
+ export function setQueryClient(client) {
171
+ globalClient = client;
172
+ }