yaxa-svelte 2.7.0 → 2.8.1

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 (56) hide show
  1. package/README.md +68 -23
  2. package/dist/auth/index.d.ts +7 -1
  3. package/dist/auth/index.js +3 -0
  4. package/dist/components/ai/ai.test.js +68 -1
  5. package/dist/components/ai/index.d.ts +2 -0
  6. package/dist/components/ai/index.js +1 -0
  7. package/dist/components/ai/useAiChat.svelte.d.ts +68 -0
  8. package/dist/components/ai/useAiChat.svelte.js +258 -0
  9. package/dist/components/blocks/FAQ.svelte +11 -0
  10. package/dist/components/blocks/FAQ.svelte.d.ts +1 -0
  11. package/dist/components/blocks/FAQ.test.js +20 -0
  12. package/dist/components/saas/AuthCard.svelte +16 -12
  13. package/dist/components/saas/AuthCard.svelte.d.ts +3 -3
  14. package/dist/components/saas/ResetPasswordCard.svelte +13 -0
  15. package/dist/components/saas/ResetPasswordCard.svelte.d.ts +5 -0
  16. package/dist/components/saas/SignInCard.svelte +13 -0
  17. package/dist/components/saas/SignInCard.svelte.d.ts +5 -0
  18. package/dist/components/saas/SignUpCard.svelte +13 -0
  19. package/dist/components/saas/SignUpCard.svelte.d.ts +5 -0
  20. package/dist/components/saas/auth-cards.test.d.ts +1 -0
  21. package/dist/components/saas/auth-cards.test.js +50 -0
  22. package/dist/components/seo/Seo.svelte +16 -9
  23. package/dist/index.d.ts +9 -2
  24. package/dist/index.js +5 -1
  25. package/dist/root-purity.test.d.ts +1 -0
  26. package/dist/root-purity.test.js +96 -0
  27. package/dist/server/ai/index.d.ts +2 -0
  28. package/dist/server/ai/index.js +1 -0
  29. package/dist/server/ai/stream.d.ts +24 -0
  30. package/dist/server/ai/stream.js +178 -0
  31. package/dist/server/ai/stream.test.d.ts +1 -0
  32. package/dist/server/ai/stream.test.js +83 -0
  33. package/dist/server/ai/types.d.ts +43 -0
  34. package/dist/server/ai/types.js +1 -0
  35. package/dist/server/index.d.ts +6 -0
  36. package/dist/server/index.js +4 -0
  37. package/dist/site/context.svelte.d.ts +3 -0
  38. package/dist/site/context.svelte.js +7 -0
  39. package/dist/site/hook.d.ts +5 -1
  40. package/dist/site/hook.js +11 -1
  41. package/dist/site/index.d.ts +26 -0
  42. package/dist/site/index.js +25 -0
  43. package/dist/site/llms.d.ts +27 -0
  44. package/dist/site/llms.js +58 -0
  45. package/dist/site/schema.d.ts +45 -0
  46. package/dist/site/schema.js +43 -0
  47. package/dist/site/seo-helpers.d.ts +1 -0
  48. package/dist/site/site-export.test.d.ts +1 -0
  49. package/dist/site/site-export.test.js +193 -0
  50. package/dist/site/skill.d.ts +26 -0
  51. package/dist/site/skill.js +69 -0
  52. package/dist/site/useSeo.svelte.d.ts +23 -0
  53. package/dist/site/useSeo.svelte.js +40 -0
  54. package/dist/site/useSeo.test.d.ts +1 -0
  55. package/dist/site/useSeo.test.js +35 -0
  56. package/package.json +5 -1
@@ -12,6 +12,7 @@ export type FAQProps = VariantProps<typeof faqVariants> & {
12
12
  badge?: string;
13
13
  items?: FAQItem[];
14
14
  categories?: string[];
15
+ enableSchema?: boolean;
15
16
  class?: string;
16
17
  };
17
18
  declare const FAQ: import("svelte").Component<FAQProps, {}, "">;
@@ -1,9 +1,29 @@
1
1
  import { describe, it, expect } from 'vitest';
2
2
  import { faqVariants } from './FAQ.svelte';
3
+ import FAQ from './FAQ.svelte';
4
+ import { renderComponent } from '../../testing/index.js';
3
5
  describe('FAQ Block', () => {
4
6
  it('generates default variant classes', () => {
5
7
  const classes = faqVariants();
6
8
  expect(classes).toContain('w-full');
7
9
  expect(classes).toContain('max-w-3xl');
8
10
  });
11
+ it('renders FAQ and injects Schema.org FAQPage json-ld when enabled', () => {
12
+ const items = [
13
+ { id: 1, question: 'How does it work?', answer: 'It works seamlessly with Svelte 5.' }
14
+ ];
15
+ const { cleanup, target } = renderComponent(FAQ, {
16
+ items,
17
+ enableSchema: true
18
+ });
19
+ expect(target.textContent).toContain('How does it work?');
20
+ expect(target.textContent).toContain('Frequently Asked Questions');
21
+ const scriptTag = document.querySelector('head script[type="application/ld+json"]');
22
+ if (scriptTag) {
23
+ const json = JSON.parse(scriptTag.textContent || '{}');
24
+ expect(json['@type']).toBe('FAQPage');
25
+ expect(json.mainEntity[0].name).toBe('How does it work?');
26
+ }
27
+ cleanup();
28
+ });
9
29
  });
@@ -1,13 +1,5 @@
1
- <script lang="ts">
2
- import Card from '../layout/Card.svelte';
3
- import Button from '../elements/Button.svelte';
4
- import Input from '../forms/Input.svelte';
5
- import FormField from '../forms/FormField.svelte';
6
- import InputOTP from '../forms/InputOTP.svelte';
7
- import Divider from '../layout/Divider.svelte';
8
- import Alert from '../overlays/Alert.svelte';
9
- import Icon from '../elements/Icon.svelte';
10
- import { useAuth, type SocialProvider } from '../../composables/useAuth.svelte';
1
+ <script module lang="ts">
2
+ import type { SocialProvider } from '../../composables/useAuth.svelte';
11
3
 
12
4
  export type AuthCardMode =
13
5
  | 'signin'
@@ -18,7 +10,7 @@
18
10
  | '2fa-verify'
19
11
  | 'email-change';
20
12
 
21
- interface Props {
13
+ export interface AuthCardProps {
22
14
  title?: string;
23
15
  subtitle?: string;
24
16
  mode?: AuthCardMode;
@@ -33,6 +25,18 @@
33
25
  onsuccess?: () => void;
34
26
  onerror?: (err: string) => void;
35
27
  }
28
+ </script>
29
+
30
+ <script lang="ts">
31
+ import Card from '../layout/Card.svelte';
32
+ import Button from '../elements/Button.svelte';
33
+ import Input from '../forms/Input.svelte';
34
+ import FormField from '../forms/FormField.svelte';
35
+ import InputOTP from '../forms/InputOTP.svelte';
36
+ import Divider from '../layout/Divider.svelte';
37
+ import Alert from '../overlays/Alert.svelte';
38
+ import Icon from '../elements/Icon.svelte';
39
+ import { useAuth } from '../../composables/useAuth.svelte';
36
40
 
37
41
  let {
38
42
  title,
@@ -48,7 +52,7 @@
48
52
  class: className = '',
49
53
  onsuccess,
50
54
  onerror
51
- }: Props = $props();
55
+ }: AuthCardProps = $props();
52
56
 
53
57
  const auth = useAuth();
54
58
 
@@ -1,6 +1,6 @@
1
- import { type SocialProvider } from '../../composables/useAuth.svelte';
1
+ import type { SocialProvider } from '../../composables/useAuth.svelte';
2
2
  export type AuthCardMode = 'signin' | 'signup' | 'magic-link' | 'forgot-password' | 'reset-password' | '2fa-verify' | 'email-change';
3
- interface Props {
3
+ export interface AuthCardProps {
4
4
  title?: string;
5
5
  subtitle?: string;
6
6
  mode?: AuthCardMode;
@@ -15,6 +15,6 @@ interface Props {
15
15
  onsuccess?: () => void;
16
16
  onerror?: (err: string) => void;
17
17
  }
18
- declare const AuthCard: import("svelte").Component<Props, {}, "">;
18
+ declare const AuthCard: import("svelte").Component<AuthCardProps, {}, "">;
19
19
  type AuthCard = ReturnType<typeof AuthCard>;
20
20
  export default AuthCard;
@@ -0,0 +1,13 @@
1
+ <script module lang="ts">
2
+ import type { AuthCardProps } from './AuthCard.svelte';
3
+
4
+ export type ResetPasswordCardProps = Omit<AuthCardProps, 'mode'>;
5
+ </script>
6
+
7
+ <script lang="ts">
8
+ import AuthCard from './AuthCard.svelte';
9
+
10
+ let props: ResetPasswordCardProps = $props();
11
+ </script>
12
+
13
+ <AuthCard mode="reset-password" {...props} />
@@ -0,0 +1,5 @@
1
+ import type { AuthCardProps } from './AuthCard.svelte';
2
+ export type ResetPasswordCardProps = Omit<AuthCardProps, 'mode'>;
3
+ declare const ResetPasswordCard: import("svelte").Component<ResetPasswordCardProps, {}, "">;
4
+ type ResetPasswordCard = ReturnType<typeof ResetPasswordCard>;
5
+ export default ResetPasswordCard;
@@ -0,0 +1,13 @@
1
+ <script module lang="ts">
2
+ import type { AuthCardProps } from './AuthCard.svelte';
3
+
4
+ export type SignInCardProps = Omit<AuthCardProps, 'mode'>;
5
+ </script>
6
+
7
+ <script lang="ts">
8
+ import AuthCard from './AuthCard.svelte';
9
+
10
+ let props: SignInCardProps = $props();
11
+ </script>
12
+
13
+ <AuthCard mode="signin" {...props} />
@@ -0,0 +1,5 @@
1
+ import type { AuthCardProps } from './AuthCard.svelte';
2
+ export type SignInCardProps = Omit<AuthCardProps, 'mode'>;
3
+ declare const SignInCard: import("svelte").Component<SignInCardProps, {}, "">;
4
+ type SignInCard = ReturnType<typeof SignInCard>;
5
+ export default SignInCard;
@@ -0,0 +1,13 @@
1
+ <script module lang="ts">
2
+ import type { AuthCardProps } from './AuthCard.svelte';
3
+
4
+ export type SignUpCardProps = Omit<AuthCardProps, 'mode'>;
5
+ </script>
6
+
7
+ <script lang="ts">
8
+ import AuthCard from './AuthCard.svelte';
9
+
10
+ let props: SignUpCardProps = $props();
11
+ </script>
12
+
13
+ <AuthCard mode="signup" {...props} />
@@ -0,0 +1,5 @@
1
+ import type { AuthCardProps } from './AuthCard.svelte';
2
+ export type SignUpCardProps = Omit<AuthCardProps, 'mode'>;
3
+ declare const SignUpCard: import("svelte").Component<SignUpCardProps, {}, "">;
4
+ type SignUpCard = ReturnType<typeof SignUpCard>;
5
+ export default SignUpCard;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,50 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import AuthCard from './AuthCard.svelte';
3
+ import SignInCard from './SignInCard.svelte';
4
+ import SignUpCard from './SignUpCard.svelte';
5
+ import ResetPasswordCard from './ResetPasswordCard.svelte';
6
+ import { AuthCard as SubpathAuthCard, SignInCard as SubpathSignInCard, SignUpCard as SubpathSignUpCard, ResetPasswordCard as SubpathResetPasswordCard, PasskeyUI, TwoFactorModal, useAuth } from '../../auth/index.js';
7
+ import { renderComponent } from '../../testing/index.js';
8
+ describe('Turnkey Auth Suite Components', () => {
9
+ it('exports all auth card component definitions from yaxa-svelte/auth', () => {
10
+ expect(AuthCard).toBeDefined();
11
+ expect(SignInCard).toBeDefined();
12
+ expect(SignUpCard).toBeDefined();
13
+ expect(ResetPasswordCard).toBeDefined();
14
+ expect(SubpathAuthCard).toBeDefined();
15
+ expect(SubpathSignInCard).toBeDefined();
16
+ expect(SubpathSignUpCard).toBeDefined();
17
+ expect(SubpathResetPasswordCard).toBeDefined();
18
+ expect(PasskeyUI).toBeDefined();
19
+ expect(TwoFactorModal).toBeDefined();
20
+ expect(typeof useAuth).toBe('function');
21
+ });
22
+ it('renders SignInCard with sign-in fields and action buttons', () => {
23
+ const { cleanup, target } = renderComponent(SignInCard, {
24
+ title: 'Welcome Back',
25
+ showSocial: true
26
+ });
27
+ expect(target.textContent).toContain('Welcome Back');
28
+ expect(target.textContent).toContain('Sign in');
29
+ expect(target.textContent).toContain('GitHub');
30
+ expect(target.textContent).toContain('Google');
31
+ cleanup();
32
+ });
33
+ it('renders SignUpCard with sign-up heading and register button', () => {
34
+ const { cleanup, target } = renderComponent(SignUpCard, {
35
+ title: 'Create Your Account'
36
+ });
37
+ expect(target.textContent).toContain('Create Your Account');
38
+ expect(target.textContent).toContain('Create Account');
39
+ cleanup();
40
+ });
41
+ it('renders ResetPasswordCard with password recovery UI', () => {
42
+ const { cleanup, target } = renderComponent(ResetPasswordCard, {
43
+ title: 'Reset Password',
44
+ resetToken: 'test-token-123'
45
+ });
46
+ expect(target.textContent).toContain('Reset Password');
47
+ expect(target.textContent).toContain('Confirm New Password');
48
+ cleanup();
49
+ });
50
+ });
@@ -39,10 +39,15 @@
39
39
 
40
40
  // Read config from prop or reactive Yaxa context
41
41
  let config = $derived(propConfig || yaxa.config);
42
+ let baseUrl = $derived((config.url || '').replace(/\/$/, ''));
42
43
 
43
- // Dynamic resolution from props OR SvelteKit page.data (e.g. +page.server.ts load data)
44
+ // Dynamic resolution from props OR useSeo() OR SvelteKit page.data (e.g. +page.server.ts load data)
44
45
  let pageData = $derived(page.data || {});
45
- let pageSeo = $derived((pageData.seo as Record<string, unknown>) || {});
46
+ let pageSeo = $derived(
47
+ (yaxa.pageSeo as unknown as Record<string, unknown>) ||
48
+ (pageData.seo as Record<string, unknown>) ||
49
+ {}
50
+ );
46
51
 
47
52
  let resolvedTitle = $derived(
48
53
  propTitle ||
@@ -73,9 +78,9 @@
73
78
  let currentUrl = $derived.by(() => {
74
79
  if (propCanonical) return propCanonical;
75
80
  try {
76
- return `${config.url}${page.url.pathname}`;
81
+ return `${baseUrl}${page.url.pathname}`;
77
82
  } catch {
78
- return config.url;
83
+ return baseUrl;
79
84
  }
80
85
  });
81
86
 
@@ -83,7 +88,9 @@
83
88
 
84
89
  let resolvedOgImageUrl = $derived.by(() => {
85
90
  if (typeof ogImage === 'string') {
86
- return ogImage.startsWith('http') ? ogImage : `${config.url}${ogImage}`;
91
+ return ogImage.startsWith('http')
92
+ ? ogImage
93
+ : `${baseUrl}${ogImage.startsWith('/') ? '' : '/'}${ogImage}`;
87
94
  }
88
95
  if (typeof ogImage === 'object' && ogImage !== null) {
89
96
  const pairs: string[] = [];
@@ -101,7 +108,7 @@
101
108
  pairs.push(`badge=${encodeURIComponent(computeProjectBadge(config.project))}`);
102
109
  }
103
110
  if (img.theme) pairs.push(`theme=${encodeURIComponent(img.theme)}`);
104
- return `${config.url}/api/og?${pairs.join('&')}`;
111
+ return `${baseUrl}/api/og?${pairs.join('&')}`;
105
112
  }
106
113
  if (resolvedTitle && resolvedTitle !== config.name) {
107
114
  const pairs: string[] = [
@@ -111,9 +118,9 @@
111
118
  if (config.project) {
112
119
  pairs.push(`badge=${encodeURIComponent(computeProjectBadge(config.project))}`);
113
120
  }
114
- return `${config.url}/api/og?${pairs.join('&')}`;
121
+ return `${baseUrl}/api/og?${pairs.join('&')}`;
115
122
  }
116
- return `${config.url}${config.seo?.defaultOgImage || '/api/og'}`;
123
+ return `${baseUrl}${config.seo?.defaultOgImage || '/api/og'}`;
117
124
  });
118
125
 
119
126
  let robotsContent = $derived.by(() => {
@@ -156,7 +163,7 @@
156
163
  <meta name="keywords" content={keywords.join(', ')} />
157
164
  {/if}
158
165
  <link rel="canonical" href={currentUrl} />
159
- <link rel="sitemap" type="application/xml" href="{config.url}/sitemap.xml" />
166
+ <link rel="sitemap" type="application/xml" href="{baseUrl}/sitemap.xml" />
160
167
  <meta name="robots" content={robotsContent} />
161
168
  <meta name="theme-color" content={config.theme?.primaryColor || '#ff3e00'} />
162
169
  {#if config.project?.license}
package/dist/index.d.ts CHANGED
@@ -45,6 +45,8 @@ export { default as AiThought } from './components/ai/AiThought.svelte';
45
45
  export type { AiThoughtProps } from './components/ai/AiThought.svelte';
46
46
  export { default as AiToolCall } from './components/ai/AiToolCall.svelte';
47
47
  export type { AiToolCallProps, ToolCallStatus } from './components/ai/AiToolCall.svelte';
48
+ export { useAiChat } from './components/ai/useAiChat.svelte';
49
+ export type { UseAiChatOptions, UseAiChatReturn } from './components/ai/useAiChat.svelte';
48
50
  export { default as LineChart } from './components/charts/LineChart.svelte';
49
51
  export type { LineChartProps, LineSeries, ChartDataPoint } from './components/charts/LineChart.svelte';
50
52
  export { default as BarChart } from './components/charts/BarChart.svelte';
@@ -181,10 +183,15 @@ export { createSitemapXslHandler, generateSitemapXsl } from './site/sitemap-xsl'
181
183
  export { createOgImageHandler, generateOgSvg } from './site/og';
182
184
  export type { OgImageOptions } from './site/og';
183
185
  export { createManifestHandler } from './site/manifest';
184
- export { generateWebSiteSchema, generateOrganizationSchema, generateSoftwareApplicationSchema, generateSoftwareSourceCodeSchema, generatePersonSchema, generateArticleSchema, generateBreadcrumbSchema } from './site/schema';
185
- export type { SchemaType, BreadcrumbItem, ArticleSchemaData } from './site/schema';
186
+ export { createLlmsTxtHandler, generateLlmsTxt } from './site/llms';
187
+ export type { LlmsTxtOptions, LlmsSection, LlmsLink } from './site/llms';
188
+ export { createSkillHandler, generateSkillMd } from './site/skill';
189
+ export type { SkillOptions, SkillAction } from './site/skill';
190
+ export { generateWebSiteSchema, generateOrganizationSchema, generateSoftwareApplicationSchema, generateSoftwareSourceCodeSchema, generatePersonSchema, generateArticleSchema, generateBreadcrumbSchema, generateFaqSchema, generateProductSchema } from './site/schema';
191
+ export type { SchemaType, BreadcrumbItem, ArticleSchemaData, FaqItem, ProductSchemaData } from './site/schema';
186
192
  export { definePageSeo } from './site/seo-helpers';
187
193
  export type { PageSeoConfig } from './site/seo-helpers';
194
+ export { useSeo } from './site/useSeo.svelte';
188
195
  export { yaxa } from './vite/index';
189
196
  export type { YaxaPluginOptions } from './vite/index';
190
197
  export { default as Gate } from './components/saas/Gate.svelte';
package/dist/index.js CHANGED
@@ -30,6 +30,7 @@ export { default as AiChat } from './components/ai/AiChat.svelte';
30
30
  export { default as PromptBar } from './components/ai/PromptBar.svelte';
31
31
  export { default as AiThought } from './components/ai/AiThought.svelte';
32
32
  export { default as AiToolCall } from './components/ai/AiToolCall.svelte';
33
+ export { useAiChat } from './components/ai/useAiChat.svelte';
33
34
  // Native Theme-Aware SVG Charts
34
35
  export { default as LineChart } from './components/charts/LineChart.svelte';
35
36
  export { default as BarChart } from './components/charts/BarChart.svelte';
@@ -120,8 +121,11 @@ export { createSitemapHandler } from './site/sitemap';
120
121
  export { createSitemapXslHandler, generateSitemapXsl } from './site/sitemap-xsl';
121
122
  export { createOgImageHandler, generateOgSvg } from './site/og';
122
123
  export { createManifestHandler } from './site/manifest';
123
- export { generateWebSiteSchema, generateOrganizationSchema, generateSoftwareApplicationSchema, generateSoftwareSourceCodeSchema, generatePersonSchema, generateArticleSchema, generateBreadcrumbSchema } from './site/schema';
124
+ export { createLlmsTxtHandler, generateLlmsTxt } from './site/llms';
125
+ export { createSkillHandler, generateSkillMd } from './site/skill';
126
+ export { generateWebSiteSchema, generateOrganizationSchema, generateSoftwareApplicationSchema, generateSoftwareSourceCodeSchema, generatePersonSchema, generateArticleSchema, generateBreadcrumbSchema, generateFaqSchema, generateProductSchema } from './site/schema';
124
127
  export { definePageSeo } from './site/seo-helpers';
128
+ export { useSeo } from './site/useSeo.svelte';
125
129
  export { yaxa } from './vite/index';
126
130
  // SaaS Suite: UI Components & Composables (Pure UI & Context Runes)
127
131
  export { default as Gate } from './components/saas/Gate.svelte';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,96 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import * as rootExports from './index';
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ describe('Root Entry Point Purity (yaxa-svelte)', () => {
6
+ const FORBIDDEN_EXPORTS = [
7
+ // Better-Auth client cards & composables (strictly yaxa-svelte/auth)
8
+ 'useAuth',
9
+ 'AuthCard',
10
+ 'SignInCard',
11
+ 'SignUpCard',
12
+ 'ResetPasswordCard',
13
+ 'PasskeyUI',
14
+ 'TwoFactorModal',
15
+ 'createYaxaAuth',
16
+ // Database & ORM (strictly yaxa-svelte/db)
17
+ 'getDb',
18
+ 'schemaPg',
19
+ 'schemaSqlite',
20
+ // Server endpoints & pipeline (strictly yaxa-svelte/server)
21
+ 'yaxaEndpoint',
22
+ 'requireAuth',
23
+ 'requireRole',
24
+ 'createAiStreamHandler',
25
+ 'createApiDocsHandler',
26
+ 'yaxaSSE',
27
+ 'broadcast',
28
+ 'enqueueJob',
29
+ 'defineWorker',
30
+ 'createJobRunner',
31
+ // Admin (strictly yaxa-svelte/admin)
32
+ 'createYaxaAdminHook',
33
+ 'createDrizzleAdmin',
34
+ 'AdminDashboard',
35
+ // Polar billing (strictly yaxa-svelte/polar)
36
+ 'createPolarCheckout',
37
+ 'createPolarWebhookHandler',
38
+ // Resend emails (strictly yaxa-svelte/email)
39
+ 'sendMagicLinkEmail',
40
+ 'sendWelcomeEmail',
41
+ // Storage uploads (strictly yaxa-svelte/storage)
42
+ 'createPresignedUploadUrl'
43
+ ];
44
+ it('must NOT export any Better-Auth, database, or server backend primitives', () => {
45
+ for (const name of FORBIDDEN_EXPORTS) {
46
+ expect(rootExports[name], `Accidental re-export of '${name}' in root 'yaxa-svelte'! Must be in appropriate subpath ('yaxa-svelte/auth', 'yaxa-svelte/server', etc.)`).toBeUndefined();
47
+ }
48
+ });
49
+ it('src/lib/index.ts source must not reference restricted subpaths or heavy backend peer dependencies', () => {
50
+ const indexPath = path.resolve('src/lib/index.ts');
51
+ const source = fs.readFileSync(indexPath, 'utf-8');
52
+ const FORBIDDEN_SUBPATHS = [
53
+ './auth',
54
+ './server',
55
+ './db',
56
+ './polar',
57
+ './email',
58
+ './storage',
59
+ './admin',
60
+ 'better-auth',
61
+ 'drizzle-orm',
62
+ '@polar-sh/sdk',
63
+ 'resend',
64
+ '@aws-sdk'
65
+ ];
66
+ for (const forbidden of FORBIDDEN_SUBPATHS) {
67
+ expect(source.includes(forbidden), `src/lib/index.ts contains forbidden reference: '${forbidden}'`).toBe(false);
68
+ }
69
+ });
70
+ it('src/lib/index.ts must not re-export auth card components', () => {
71
+ const indexPath = path.resolve('src/lib/index.ts');
72
+ const source = fs.readFileSync(indexPath, 'utf-8');
73
+ const FORBIDDEN_AUTH_CARDS = [
74
+ 'AuthCard',
75
+ 'SignInCard',
76
+ 'SignUpCard',
77
+ 'ResetPasswordCard',
78
+ 'PasskeyUI',
79
+ 'TwoFactorModal',
80
+ 'useAuth'
81
+ ];
82
+ for (const card of FORBIDDEN_AUTH_CARDS) {
83
+ const regex = new RegExp(`\\b${card}\\b`);
84
+ expect(regex.test(source), `src/lib/index.ts contains forbidden auth component: '${card}'`).toBe(false);
85
+ }
86
+ });
87
+ it('root entry point successfully evaluates without throwing runtime errors', () => {
88
+ expect(rootExports).toBeDefined();
89
+ expect(typeof rootExports.Button).toBe('function');
90
+ expect(typeof rootExports.Modal).toBe('function');
91
+ expect(typeof rootExports.Gate).toBe('function');
92
+ expect(typeof rootExports.UserMenu).toBe('function');
93
+ expect(typeof rootExports.useGate).toBe('function');
94
+ expect(typeof rootExports.useYaxa).toBe('function');
95
+ });
96
+ });
@@ -0,0 +1,2 @@
1
+ export { createAiStreamHandler } from './stream';
2
+ export type { AiStreamChunk, AiStreamContext, AiStreamHandlerFn, CreateAiStreamHandlerOptions } from './types';
@@ -0,0 +1 @@
1
+ export { createAiStreamHandler } from './stream';
@@ -0,0 +1,24 @@
1
+ import type { RequestHandler } from '@sveltejs/kit';
2
+ import type { CreateAiStreamHandlerOptions } from './types';
3
+ /**
4
+ * Creates a turnkey SvelteKit RequestHandler for AI chat streaming.
5
+ * Supports Server-Sent Events (SSE), multi-provider backends (Gemini, OpenAI, Anthropic, Ollama),
6
+ * guarded endpoints (auth/rate-limiting), and a built-in zero-config mock generator for dev/demos.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * // src/routes/api/ai/chat/+server.ts
11
+ * import { createAiStreamHandler, requireAuth } from 'yaxa-svelte/server';
12
+ *
13
+ * export const POST = createAiStreamHandler({
14
+ * guards: [requireAuth()],
15
+ * handler: async ({ messages, send }) => {
16
+ * // Integrate your favorite LLM SDK
17
+ * for await (const chunk of streamText({ model: 'gemini-2.5-pro', messages })) {
18
+ * send({ type: 'text', delta: chunk.text });
19
+ * }
20
+ * }
21
+ * });
22
+ * ```
23
+ */
24
+ export declare function createAiStreamHandler(options?: CreateAiStreamHandlerOptions): RequestHandler;
@@ -0,0 +1,178 @@
1
+ import { problemResponse } from '../endpoint/problem';
2
+ /**
3
+ * Creates a turnkey SvelteKit RequestHandler for AI chat streaming.
4
+ * Supports Server-Sent Events (SSE), multi-provider backends (Gemini, OpenAI, Anthropic, Ollama),
5
+ * guarded endpoints (auth/rate-limiting), and a built-in zero-config mock generator for dev/demos.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * // src/routes/api/ai/chat/+server.ts
10
+ * import { createAiStreamHandler, requireAuth } from 'yaxa-svelte/server';
11
+ *
12
+ * export const POST = createAiStreamHandler({
13
+ * guards: [requireAuth()],
14
+ * handler: async ({ messages, send }) => {
15
+ * // Integrate your favorite LLM SDK
16
+ * for await (const chunk of streamText({ model: 'gemini-2.5-pro', messages })) {
17
+ * send({ type: 'text', delta: chunk.text });
18
+ * }
19
+ * }
20
+ * });
21
+ * ```
22
+ */
23
+ export function createAiStreamHandler(options = {}) {
24
+ const mockConfig = options.mock === true || (options.mock === undefined && !options.handler)
25
+ ? { delayMs: 25 }
26
+ : typeof options.mock === 'object'
27
+ ? { delayMs: options.mock.delayMs ?? 25 }
28
+ : null;
29
+ return async (event) => {
30
+ const { request, locals } = event;
31
+ // 1. Evaluate guards if configured
32
+ if (options.guards && options.guards.length > 0) {
33
+ for (const guard of options.guards) {
34
+ const result = await guard(event);
35
+ if (result instanceof Response) {
36
+ return result;
37
+ }
38
+ if (result && typeof result === 'object' && 'status' in result && 'title' in result) {
39
+ return problemResponse(result);
40
+ }
41
+ if (result === false) {
42
+ return problemResponse({
43
+ status: 403,
44
+ title: 'Forbidden',
45
+ detail: 'Access denied by endpoint guard.'
46
+ });
47
+ }
48
+ }
49
+ }
50
+ // 2. Parse request payload
51
+ let messages = [];
52
+ try {
53
+ const body = await request.json();
54
+ messages = Array.isArray(body?.messages) ? body.messages : [];
55
+ }
56
+ catch {
57
+ return problemResponse({
58
+ status: 400,
59
+ title: 'Bad Request',
60
+ detail: 'Invalid JSON request body. Expected an object with a "messages" array.'
61
+ });
62
+ }
63
+ await options.onStart?.(event);
64
+ const user = locals?.user ?? null;
65
+ const encoder = new TextEncoder();
66
+ const signal = request.signal;
67
+ const stream = new ReadableStream({
68
+ async start(controller) {
69
+ let isClosed = false;
70
+ function safeEnqueue(str) {
71
+ if (!isClosed && !signal.aborted) {
72
+ try {
73
+ controller.enqueue(encoder.encode(str));
74
+ }
75
+ catch {
76
+ isClosed = true;
77
+ }
78
+ }
79
+ }
80
+ function sendChunk(chunk) {
81
+ safeEnqueue(`data: ${JSON.stringify(chunk)}\n\n`);
82
+ }
83
+ const ctx = {
84
+ event,
85
+ messages,
86
+ user,
87
+ signal,
88
+ send: sendChunk
89
+ };
90
+ try {
91
+ if (options.handler) {
92
+ const res = await options.handler(ctx);
93
+ if (res && typeof res[Symbol.asyncIterator] === 'function') {
94
+ for await (const chunk of res) {
95
+ if (signal.aborted)
96
+ break;
97
+ sendChunk(chunk);
98
+ }
99
+ }
100
+ }
101
+ else if (mockConfig) {
102
+ // Simulated Realistic AI Flow: Thought -> Streamed Text -> Done
103
+ const lastUserMsg = [...messages].reverse().find((m) => m.role === 'user');
104
+ const prompt = lastUserMsg?.content || 'Hello';
105
+ // 1. Thought start
106
+ sendChunk({
107
+ type: 'thought',
108
+ thought: {
109
+ running: true,
110
+ text: 'Analyzing request and inspecting context...',
111
+ steps: ['Interpreting prompt semantics', 'Checking relevant system tools']
112
+ }
113
+ });
114
+ await new Promise((r) => setTimeout(r, Math.max(10, mockConfig.delayMs * 2)));
115
+ if (signal.aborted)
116
+ return;
117
+ // 2. Thought finish
118
+ sendChunk({
119
+ type: 'thought',
120
+ thought: {
121
+ running: false,
122
+ duration: 0.8,
123
+ text: 'Context analyzed. Formulating response.',
124
+ steps: ['Interpreting prompt semantics', 'Synthesizing output response']
125
+ }
126
+ });
127
+ // 3. Stream response tokens
128
+ const mockText = `I have received your request: "${prompt}".\n\nThis response is streamed live via Yaxa's \`createAiStreamHandler\` using Server-Sent Events (SSE). You can connect this seamlessly with OpenAI, Google Gemini, Anthropic, or local Ollama models.`;
129
+ const words = mockText.split(' ');
130
+ for (let i = 0; i < words.length; i++) {
131
+ if (signal.aborted)
132
+ break;
133
+ const word = words[i] + (i === words.length - 1 ? '' : ' ');
134
+ sendChunk({ type: 'text', delta: word });
135
+ if (mockConfig.delayMs > 0) {
136
+ await new Promise((r) => setTimeout(r, mockConfig.delayMs));
137
+ }
138
+ }
139
+ if (!signal.aborted) {
140
+ sendChunk({ type: 'done' });
141
+ }
142
+ }
143
+ if (!isClosed) {
144
+ isClosed = true;
145
+ controller.close();
146
+ }
147
+ }
148
+ catch (err) {
149
+ if (!isClosed && !signal.aborted) {
150
+ sendChunk({
151
+ type: 'error',
152
+ error: err instanceof Error ? err.message : String(err)
153
+ });
154
+ isClosed = true;
155
+ try {
156
+ controller.close();
157
+ }
158
+ catch {
159
+ // Controller may already be closed
160
+ }
161
+ }
162
+ await options.onError?.(err, event);
163
+ }
164
+ },
165
+ cancel() {
166
+ // Client cancelled stream
167
+ }
168
+ });
169
+ return new Response(stream, {
170
+ headers: {
171
+ 'Content-Type': 'text/event-stream; charset=utf-8',
172
+ 'Cache-Control': 'no-cache, no-transform',
173
+ Connection: 'keep-alive',
174
+ 'X-Accel-Buffering': 'no'
175
+ }
176
+ });
177
+ };
178
+ }