yaxa-svelte 2.7.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.
- package/README.md +66 -21
- package/dist/auth/index.d.ts +7 -1
- package/dist/auth/index.js +3 -0
- package/dist/components/ai/ai.test.js +68 -1
- package/dist/components/ai/index.d.ts +2 -0
- package/dist/components/ai/index.js +1 -0
- package/dist/components/ai/useAiChat.svelte.d.ts +68 -0
- package/dist/components/ai/useAiChat.svelte.js +258 -0
- package/dist/components/blocks/FAQ.svelte +11 -0
- package/dist/components/blocks/FAQ.svelte.d.ts +1 -0
- package/dist/components/blocks/FAQ.test.js +20 -0
- package/dist/components/saas/AuthCard.svelte +16 -12
- package/dist/components/saas/AuthCard.svelte.d.ts +3 -3
- package/dist/components/saas/ResetPasswordCard.svelte +13 -0
- package/dist/components/saas/ResetPasswordCard.svelte.d.ts +5 -0
- package/dist/components/saas/SignInCard.svelte +13 -0
- package/dist/components/saas/SignInCard.svelte.d.ts +5 -0
- package/dist/components/saas/SignUpCard.svelte +13 -0
- package/dist/components/saas/SignUpCard.svelte.d.ts +5 -0
- package/dist/components/saas/auth-cards.test.d.ts +1 -0
- package/dist/components/saas/auth-cards.test.js +42 -0
- package/dist/components/seo/Seo.svelte +16 -9
- package/dist/index.d.ts +21 -2
- package/dist/index.js +12 -1
- package/dist/server/ai/index.d.ts +2 -0
- package/dist/server/ai/index.js +1 -0
- package/dist/server/ai/stream.d.ts +24 -0
- package/dist/server/ai/stream.js +178 -0
- package/dist/server/ai/stream.test.d.ts +1 -0
- package/dist/server/ai/stream.test.js +83 -0
- package/dist/server/ai/types.d.ts +43 -0
- package/dist/server/ai/types.js +1 -0
- package/dist/server/index.d.ts +6 -0
- package/dist/server/index.js +4 -0
- package/dist/site/context.svelte.d.ts +3 -0
- package/dist/site/context.svelte.js +7 -0
- package/dist/site/hook.d.ts +5 -1
- package/dist/site/hook.js +11 -1
- package/dist/site/index.d.ts +26 -0
- package/dist/site/index.js +25 -0
- package/dist/site/llms.d.ts +27 -0
- package/dist/site/llms.js +58 -0
- package/dist/site/schema.d.ts +45 -0
- package/dist/site/schema.js +43 -0
- package/dist/site/seo-helpers.d.ts +1 -0
- package/dist/site/site-export.test.d.ts +1 -0
- package/dist/site/site-export.test.js +193 -0
- package/dist/site/skill.d.ts +26 -0
- package/dist/site/skill.js +69 -0
- package/dist/site/useSeo.svelte.d.ts +23 -0
- package/dist/site/useSeo.svelte.js +40 -0
- package/dist/site/useSeo.test.d.ts +1 -0
- package/dist/site/useSeo.test.js +35 -0
- package/package.json +5 -1
|
@@ -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
|
|
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
|
|
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
|
-
}:
|
|
55
|
+
}: AuthCardProps = $props();
|
|
52
56
|
|
|
53
57
|
const auth = useAuth();
|
|
54
58
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
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
|
|
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<
|
|
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,42 @@
|
|
|
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 { renderComponent } from '../../testing/index.js';
|
|
7
|
+
describe('Turnkey Auth Suite Components', () => {
|
|
8
|
+
it('exports all auth card component definitions', () => {
|
|
9
|
+
expect(AuthCard).toBeDefined();
|
|
10
|
+
expect(SignInCard).toBeDefined();
|
|
11
|
+
expect(SignUpCard).toBeDefined();
|
|
12
|
+
expect(ResetPasswordCard).toBeDefined();
|
|
13
|
+
});
|
|
14
|
+
it('renders SignInCard with sign-in fields and action buttons', () => {
|
|
15
|
+
const { cleanup, target } = renderComponent(SignInCard, {
|
|
16
|
+
title: 'Welcome Back',
|
|
17
|
+
showSocial: true
|
|
18
|
+
});
|
|
19
|
+
expect(target.textContent).toContain('Welcome Back');
|
|
20
|
+
expect(target.textContent).toContain('Sign in');
|
|
21
|
+
expect(target.textContent).toContain('GitHub');
|
|
22
|
+
expect(target.textContent).toContain('Google');
|
|
23
|
+
cleanup();
|
|
24
|
+
});
|
|
25
|
+
it('renders SignUpCard with sign-up heading and register button', () => {
|
|
26
|
+
const { cleanup, target } = renderComponent(SignUpCard, {
|
|
27
|
+
title: 'Create Your Account'
|
|
28
|
+
});
|
|
29
|
+
expect(target.textContent).toContain('Create Your Account');
|
|
30
|
+
expect(target.textContent).toContain('Create Account');
|
|
31
|
+
cleanup();
|
|
32
|
+
});
|
|
33
|
+
it('renders ResetPasswordCard with password recovery UI', () => {
|
|
34
|
+
const { cleanup, target } = renderComponent(ResetPasswordCard, {
|
|
35
|
+
title: 'Reset Password',
|
|
36
|
+
resetToken: 'test-token-123'
|
|
37
|
+
});
|
|
38
|
+
expect(target.textContent).toContain('Reset Password');
|
|
39
|
+
expect(target.textContent).toContain('Confirm New Password');
|
|
40
|
+
cleanup();
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -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(
|
|
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 `${
|
|
81
|
+
return `${baseUrl}${page.url.pathname}`;
|
|
77
82
|
} catch {
|
|
78
|
-
return
|
|
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')
|
|
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 `${
|
|
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 `${
|
|
121
|
+
return `${baseUrl}/api/og?${pairs.join('&')}`;
|
|
115
122
|
}
|
|
116
|
-
return `${
|
|
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="{
|
|
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 {
|
|
185
|
-
export type {
|
|
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';
|
|
@@ -195,6 +202,18 @@ export type { OrgSwitcherProps, OrgItem } from './components/saas/OrgSwitcher.sv
|
|
|
195
202
|
export { default as PricingCard } from './components/saas/PricingCard.svelte';
|
|
196
203
|
export { default as PricingTable } from './components/saas/PricingTable.svelte';
|
|
197
204
|
export { default as SubscriptionCard } from './components/saas/SubscriptionCard.svelte';
|
|
205
|
+
export { default as AuthCard } from './components/saas/AuthCard.svelte';
|
|
206
|
+
export type { AuthCardProps, AuthCardMode } from './components/saas/AuthCard.svelte';
|
|
207
|
+
export { default as SignInCard } from './components/saas/SignInCard.svelte';
|
|
208
|
+
export type { SignInCardProps } from './components/saas/SignInCard.svelte';
|
|
209
|
+
export { default as SignUpCard } from './components/saas/SignUpCard.svelte';
|
|
210
|
+
export type { SignUpCardProps } from './components/saas/SignUpCard.svelte';
|
|
211
|
+
export { default as ResetPasswordCard } from './components/saas/ResetPasswordCard.svelte';
|
|
212
|
+
export type { ResetPasswordCardProps } from './components/saas/ResetPasswordCard.svelte';
|
|
213
|
+
export { default as PasskeyUI } from './components/saas/PasskeyUI.svelte';
|
|
214
|
+
export { default as TwoFactorModal } from './components/saas/TwoFactorModal.svelte';
|
|
215
|
+
export { useAuth } from './composables/useAuth.svelte';
|
|
216
|
+
export type { SocialProvider } from './composables/useAuth.svelte';
|
|
198
217
|
export { useGate } from './composables/useGate.svelte';
|
|
199
218
|
export type { UseGateOptions } from './composables/useGate.svelte';
|
|
200
219
|
export { useUpload } from './composables/useUpload.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 {
|
|
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';
|
|
@@ -130,6 +134,13 @@ export { default as OrgSwitcher, orgSwitcherVariants } from './components/saas/O
|
|
|
130
134
|
export { default as PricingCard } from './components/saas/PricingCard.svelte';
|
|
131
135
|
export { default as PricingTable } from './components/saas/PricingTable.svelte';
|
|
132
136
|
export { default as SubscriptionCard } from './components/saas/SubscriptionCard.svelte';
|
|
137
|
+
export { default as AuthCard } from './components/saas/AuthCard.svelte';
|
|
138
|
+
export { default as SignInCard } from './components/saas/SignInCard.svelte';
|
|
139
|
+
export { default as SignUpCard } from './components/saas/SignUpCard.svelte';
|
|
140
|
+
export { default as ResetPasswordCard } from './components/saas/ResetPasswordCard.svelte';
|
|
141
|
+
export { default as PasskeyUI } from './components/saas/PasskeyUI.svelte';
|
|
142
|
+
export { default as TwoFactorModal } from './components/saas/TwoFactorModal.svelte';
|
|
143
|
+
export { useAuth } from './composables/useAuth.svelte';
|
|
133
144
|
export { useGate } from './composables/useGate.svelte';
|
|
134
145
|
export { useUpload } from './composables/useUpload.svelte';
|
|
135
146
|
// Pre-Built SaaS Blocks
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|